File: RequestedMetrics.py

package info (click to toggle)
nvidia-cuda-toolkit 12.4.1-3
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 18,505,836 kB
  • sloc: ansic: 203,477; cpp: 64,769; python: 34,699; javascript: 22,006; xml: 13,410; makefile: 3,085; sh: 2,343; perl: 352
file content (263 lines) | stat: -rw-r--r-- 9,318 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
from collections import UserDict, namedtuple

import NvRules


# TODO: switch to enum.Enum once this is available in static interpreter
class Importance:
    OPTIONAL = 1
    REQUIRED = 2


_MetricRequest = namedtuple(
    "MetricRequest",
    ["name", "alias", "importance", "default_value", "warn_when_missing"],
    defaults=(None, None, Importance.REQUIRED, 0.0, True),
)


class MetricRequest(_MetricRequest):
    """A metric request containing name, and optional alias, importance and default value.

    Args:
        name (str): The name of the metric.
        alias (str, optional): An alias for the metric name. Defaults to None.
        importance (:obj:`Importance`, optional): Whether the metric is required or
            optional. Defaults to Importance.REQUIRED.
        default_value (int, float, string, optional): A default value for OPTIONAL
            metrics. Defaults to float(0).
        warn_when_missing (bool, optional): Whether to issue a warning when an OPTIONAL
            metric is missing. Defaults to True.
    """

    pass


class MetricNotFoundError(Exception):
    """Exception raised when metric cannot be found.

    Args:
        name (str): The name of the metric that was not found.
        importance (:obj:`Importance`): Whether the metric was marked
            required or optional.
        message (str): A message explaining the error
    """

    def __init__(self, name, importance, message=None):
        importance_str = "Required" if importance == Importance.REQUIRED else "Optional"
        default_message = "{} metric {} could not be found.".format(importance_str, name)

        self.name = name
        self.importance = importance
        self.message = message or default_message
        super().__init__(self.message)


class RequestedMetric:
    """Wrapper class for available (loaded) metrics.

    Args:
        name (str): The name of the metric.
        metric (:obj:`IMetric`): An IMetric object representing the metric.
        importance (:obj:`Importance`, optional): Whether the metric was marked
            required or optional. Defaults to Importance.REQUIRED.
        alias (str, optional): An alias for the metric name. Defaults to None.
    """

    _DEFAULT_IMPORTANCE = Importance.REQUIRED

    def __init__(self, name, metric, importance=None, alias=None):
        self._name = name
        self._metric = metric
        self._importance = importance or RequestedMetric._DEFAULT_IMPORTANCE
        self._alias = alias

    @property
    def name(self):
        return self._name

    @property
    def metric(self):
        return self._metric

    @property
    def importance(self):
        return self._importance

    @property
    def alias(self):
        return self._alias


class RequestedMetricBuilder:
    """Builds a RequestedMetric from an IMetric contained in an IAction.

    Args:
        metrics (:obj:`IAction`): An IAction object (potentially) containing the IMetric
            associated with `name`.
        name (str): The name of the metric.
        importance (:obj:`Importance`, optional): Whether the metric was marked
            required or optional. Defaults to Importance.REQUIRED.
        alias (str, optional): An alias for the metric name. Defaults to None.

    Raises:
        MetricNotFoundError: If no IMetric object associated with `name` is available.
    """

    def __init__(self, metrics, name, importance=Importance.REQUIRED, alias=None):
        self._metric = None
        try:
            metric = metrics[name]
            self._metric = RequestedMetric(name, metric, importance, alias)
        except KeyError:
            raise MetricNotFoundError(name, importance)

    """Returns the RequestedMetric object built."""

    def build(self):
        return self._metric


class RequestedMetricDict(UserDict):
    """Custom dict for IMetric lookup using either the metric's name or alias.

    Allows insertion of RequestedMetric values using a key that matches either
    the metric's name or its alias. Lookup will return the IMetric object directly.
    Lookup can be done using name or alias, independently of how the RequestedMetric
    was inserted.
    """

    def __init__(self):
        self.aliasToName = {}
        super().__init__()

    def __getitem__(self, key):
        try:
            return super().__getitem__(key).metric
        except KeyError:
            pass
        try:
            return super().__getitem__(self.aliasToName[key]).metric
        except KeyError:
            pass
        raise KeyError(key)

    def __setitem__(self, key, item):
        name = item.name
        alias = item.alias

        if (key != name) and (key != alias):
            raise KeyError("Key must match either the metric's name or alias.")

        if alias is not None:
            # check whether alias is already used by another metric
            if (alias in self.aliasToName) and (self.aliasToName[alias] != name):
                raise KeyError("Alias {} is already used by metric {}".format(alias, key))
            # save the alias for later lookup
            self.aliasToName[alias] = name

        return super().__setitem__(name, item)

    def __contains__(self, key):
        is_alias = key in self.aliasToName
        return super().__contains__(key) or is_alias


class RequestedMetricsParser:
    """Convenience class to query IMetric objects from an IAction.

    Args:
        handle: The NvRules ContextHandle.
        action (:obj:`IAction`): IAction object containing the IMetrics to be requested.
    """

    _MISSING_REQUIRED_METRICS_MESSAGE = (
        "Some required metrics are missing; aborted rule execution."
    )

    def __init__(self, handle, action):
        self.handle = handle
        self.frontend = NvRules.get_context(handle).frontend()
        self.metrics = action

    def parse(self, requested_metrics):
        """Parse a list of `MetricRequest`s and return a custom dict of `IMetric` objects.

        Args:
            requested_metrics (List[MetricRequest]): A list of requested metrics.

        Returns:
            A RequestedMetricDict, which returns an IMetric object for any valid metric
                name or alias.

        Raises:
            SystemError: If any REQUIRED metric is not contained in the IAction object.
        """
        parsed_metrics = RequestedMetricDict()
        found_missing_required_metrics = False
        found_missing_optional_metrics = False

        for metric in requested_metrics:
            try:
                parsed_metrics[metric.name] = RequestedMetricBuilder(
                    name=metric.name,
                    metrics=self.metrics,
                    importance=metric.importance,
                    alias=metric.alias,
                ).build()
            except MetricNotFoundError as error:
                if error.importance == Importance.OPTIONAL:
                    parsed_metrics[metric.name] = RequestedMetric(
                        name=metric.name,
                        metric=self._create_fallback_metric(metric),
                        importance=metric.importance,
                        alias=metric.alias,
                    )
                    # issue a warning for the first missing optional metric (by default)
                    if metric.warn_when_missing and not found_missing_optional_metrics:
                        self.frontend.message(
                            NvRules.IFrontend.MsgType_MSG_WARNING,
                            self._get_missing_optional_metric_warning(error),
                        )
                        found_missing_optional_metrics = True
                elif error.importance == Importance.REQUIRED:
                    found_missing_required_metrics = True
                    self.frontend.message(
                        NvRules.IFrontend.MsgType_MSG_ERROR,
                        self._get_missing_required_metric_warning(error),
                    )

        if found_missing_required_metrics:
            NvRules.raise_exception(
                self.handle, RequestedMetricsParser._MISSING_REQUIRED_METRICS_MESSAGE
            )

        return parsed_metrics

    def _create_fallback_metric(self, metric_request):
        if metric_request.default_value is None:
            return None

        value = metric_request.default_value
        fallback_metric = self.metrics.add_metric(metric_request.name)

        if isinstance(value, int) and value >= 0:
            fallback_metric.set_uint64(NvRules.IMetric.ValueKind_UINT64, value)
        elif isinstance(value, float):
            fallback_metric.set_double(NvRules.IMetric.ValueKind_DOUBLE, value)
        elif isinstance(value, str):
            fallback_metric.set_string(NvRules.IMetric.ValueKind_STRING, value)
        else:
            raise ValueError("Can only create fallback metric from uint, float or str.")

        return fallback_metric

    def _get_missing_optional_metric_warning(self, error):
        return str(
            "The optional metric {} could not be found. "
            "Collecting it as an additional metric could enable the rule "
            "to provide more guidance.".format(error.name)
        )

    def _get_missing_required_metric_warning(self, error):
        return error.message