ATLAS Offline Software
virtual_mixin.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
2 
3 from logging import getLogger; log = getLogger("DQDefects.virtual_defect_mixin")
4 from DQUtils import fetch_iovs
5 
6 from .exceptions import DefectUnknownError, DefectExistsError
7 from .ids import choose_new_defect_id
8 from .virtual_logic import DefectLogic
9 
10 from typing import Optional, Iterable, Mapping, Set, Sequence, List
11 
12 
13 NONHEAD_MODIFICATION_MSG = ("Operations which modify virtual defects can only "
14  "be done on the HEAD tag.")
15 
16 def _resolve_dependencies(node, resolved=None, unresolved=None):
17  """
18  Navigate the node to determine dependencies. Circular references raise an
19  exception.
20 
21  Shamelessly stolen algorithm from:
22  http://www.electricmonk.nl/log/2008/08/07/dependency-resolving-algorithm/
23  """
24  if resolved is None: resolved = []
25  if unresolved is None: unresolved = []
26  unresolved.append(node)
27  for dependency in node.dependencies:
28  if dependency not in resolved:
29  if dependency in unresolved:
30  raise Exception('Circular reference detected: %s -> %s'
31  % (node.name, dependency.name))
32  _resolve_dependencies(dependency, resolved, unresolved)
33 
34  resolved.append(node)
35  unresolved.remove(node)
36  return resolved
37 
38 
40  """
41  A DefectsDB mixin for managing virtual defects
42  """
43 
44  def __init__(self) -> None:
46  super(DefectsDBVirtualDefectsMixin, self).__init__()
47 
48  def validate_clauses(self, clauses: str) -> None:
49  all_defects = self.defect_names | self.virtual_defect_names
50  for clause in clauses.split():
51  if clause[0] in ('!', '-'):
52  clause = clause[1:]
53  assert clause in all_defects, (clause + " is not a known defect")
54 
55  def update_virtual_defect(self, defect_name: str, clauses: str, comment: Optional[str] = None) -> None:
56  # HEAD protection is here, to allow other functions to
57  # change other tags in a known manner with _update_virtual_defect
58  assert self.logics_tag == "HEAD", NONHEAD_MODIFICATION_MSG
59  assert not self._read_only, "Insertion on read-only database"
60  self._update_virtual_defect(defect_name, clauses, comment)
61 
62  def _update_virtual_defect(self, defect_name: str, clauses: str,
63  comment: Optional[str] = None, tag: Optional[str] = None) -> None:
64  defect_id = self.defect_chan_as_id(defect_name)
65 
66  assert self.defect_is_virtual(defect_id), ("Tried to update nonvirtual"
67  " defect with update_virtual_defect()")
68 
69  self.validate_clauses(clauses)
70 
71  tag = tag if tag is not None else 'HEAD'
72  ucomment = comment.encode('utf-8') if comment is not None else None
73 
74  self._defect_logic_payload["clauses"] = clauses.encode('ascii')
75 
76  store = self.defect_logic_folder.storeObject
77  store(0, 2**63-1, self._defect_logic_payload, defect_id, tag.encode('ascii'),
78  (True if tag != 'HEAD' else False))
79 
80  if comment is not None:
81  self.defect_logic_folder.setChannelDescription(defect_id, ucomment)
82 
83  def rename_defect(self, defect_name: str, new_defect_name: str) -> None:
84  """
85  Rename a defect (primary or virtual). Will keep data and channel ID.
86  Will fix up all virtual defect dependencies in all tags.
87  """
88  assert not self._read_only, "Channel rename on read-only database"
89 
90  try:
91  oldname = self.normalize_defect_names(new_defect_name)
92  raise DefectExistsError('Defect %s already exists' % oldname)
93  except DefectUnknownError:
94  pass
95 
96  defect_id = self.defect_chan_as_id(defect_name)
97 
98  anew_defect_name = new_defect_name.encode('ascii')
99  if self.defect_is_virtual(defect_id):
100  self.defect_logic_folder.setChannelName(defect_id, anew_defect_name)
103  self._virtual_defect_logics = None
104  self._virtual_initialized = False
105  else:
106  self.defects_folder.setChannelName(defect_id, anew_defect_name)
108  self._defect_id_map = {}
109  self._initialized = False
110 
111  import contextlib
112  @contextlib.contextmanager
113  def logics_unlocking(tag):
114  atag = tag.encode('ascii')
115  log.info('Unlocking tag %s', tag)
116  folder = self.defect_logic_folder
117  orig_status = folder.tagLockStatus(atag)
118  if orig_status != 0:
119  folder.setTagLockStatus(atag, 0)
120  yield
121  if orig_status != 0:
122  folder.setTagLockStatus(atag, orig_status)
123  log.info('Done with tag %s', tag)
124 
125  def logic_substitute(clause, oldname, newname):
126  oclause = clause
127  prefix = ''
128  if clause[0] in ('!',):
129  prefix = clause[0]
130  clause = clause[1:]
131  if clause != oldname:
132  return oclause
133  else:
134  return prefix + newname
135 
136  for tag in ['HEAD'] + self.logics_tags:
137  logics = self._get_virtual_defect_logics(tag)
138  for vd, logic in logics.items():
139  if defect_name in logic.realclauses:
140  newclauses = ' '.join([logic_substitute(x, defect_name,
141  new_defect_name)
142  for x in logic.clauses
143  ])
144  with logics_unlocking(tag):
145  self._update_virtual_defect(vd, newclauses, tag=tag)
146  self._virtual_defect_logics = None
147 
148  def new_virtual_defect(self, defect_name: str, comment: Optional[str], clauses: str) -> int:
149  """
150  Create a new virtual defect
151  """
152  assert self.logics_tag == "HEAD", NONHEAD_MODIFICATION_MSG
153  assert not self._read_only, "Insertion on read-only database"
154  from DQUtils.channel_mapping import get_channel_ids_names
155 
156  # Force load of defects_folder to populate _defect_payload
157  store = self.defect_logic_folder.storeObject
158  p = self._defect_logic_payload
159 
160  self.validate_clauses(clauses)
161 
162  p["clauses"] = clauses.encode('ascii')
163 
164  # need to get true mapping of folder, not just what is visible
165  # from this tag
166  ids, _, mapping = get_channel_ids_names(self.defect_logic_folder)
167  defect_id = choose_new_defect_id(mapping, defect_name, True)
168  log.debug("Creating new defect %s: 0x%08x", defect_name, defect_id)
169 
170  try:
171  oldname = self.normalize_defect_names(defect_name)
172  raise DefectExistsError('Defect %s already exists' % oldname)
173  except DefectUnknownError:
174  pass
175 
176  if (defect_id in ids):
177  raise DefectExistsError(defect_name)
178  self.defect_logic_folder.createChannel(defect_id, defect_name.encode('ascii'), comment.encode('utf-8'))
179  store(0, 2**63-1, p, defect_id)
180  self._new_virtual_defect(defect_id, defect_name)
181  return defect_id
182 
183  def _resolve_evaluation_order(self, defects: Optional[Iterable[str]] = None) -> List[DefectLogic]:
184  """
185  Returns a list of DefectLogic objects that need to be evaluated,
186  in the correct order for them to be consistent.
187 
188  `defects` should be a list of names
189  """
190  if defects is None:
191  logics = self.virtual_defect_logics.items()
192  else:
193  # flags begins as a list of names.
194  # Make it a list of logic objects instead
195  # If it is not in the logics map, the logic is not defined for the
196  # query interval and we shouldn't try to resolve it.
197 
198 
199  logics = [self.virtual_defect_logics[d] for d in defects]
200 
201  class MasterNode(object):
202  dependencies = logics
203 
204  resolved = _resolve_dependencies(MasterNode)
205  resolved.remove(MasterNode)
206  return resolved
207 
208  def resolve_primary_defects(self, defect_logics: Iterable[DefectLogic]) -> Set[str]:
209  """
210  Determine which primary flags are used for
211  a given list of input `virtual_defect_names`.
212  """
213  primary_defects = set()
214  for defect_logic in defect_logics:
215  primary_defects |= defect_logic.primary_defects
216 
217  return primary_defects
218 
219  @property
220  def virtual_defect_logics(self) -> Mapping[str, DefectLogic]:
221  """
222  Returns all virtual defects in the form {"defect_name" : DefectLogic()}
223  for the tag DefectDB was constructed on.
224  """
225  if self._virtual_defect_logics is not None:
226  return self._virtual_defect_logics
227 
229 
230  return self._virtual_defect_logics
231 
232  def _get_virtual_defect_logics(self, tag: str) -> Mapping[str, DefectLogic]:
233  if tag != "HEAD" and not self.defect_logic_folder.existsUserTag(tag.encode('ascii')):
234  # The tag doesn't exist, so there is nothing to fetch.
235  return {}
236 
237  logics = fetch_iovs(self.defect_logic_folder, tag=tag.encode('ascii'),
238  named_channels=True)
239 
240  logics = {l.channel: DefectLogic(l) for l in logics}
241 
242  for _, defect_logic in logics.items():
243  defect_logic._populate(logics)
244 
245  return logics
246 
247  def get_intolerable_defects(self, old_primary_only: bool = True,
248  exclude: Sequence[str] = ['TIGHT', 'IDTIGHT', 'PHYS_.*']):
249  """
250  Returns primary defects that are depended on by a virtual defect
251  if old_primary_only == True, only return those depended on by a
252  virtual defect with no _ in the name
253  ignores virtual defects in exclude list
254  accepts regexes in exclude list
255  """
256  vdl = self.virtual_defect_logics
257  if old_primary_only:
258  vdl = dict(l for l in vdl.items() if '_' not in l[0])
259  if len(exclude) > 0:
260  import re
261  rex = re.compile('(%s)' % '|'.join(exclude))
262  vdl = dict(l for l in vdl.items() if not rex.match(l[0]))
263  return self.resolve_primary_defects(vdl.values())
264 
266  """
267  When called, uses an assertion to check that there are no missing
268  defects. This is a database consistency check which should never be
269  violated (but was when DQDefects was new).
270  """
271  all_defects = self.defect_names | self.virtual_defect_names
272  used_defects = set()
273 
274  used_by = {}
275  for defect, logic in self.virtual_defect_logics.items():
276  used_defects.update(logic.realclauses)
277  for clause in logic.realclauses:
278  used_by.setdefault(clause, []).append(defect)
279 
280  missing_defects = used_defects - all_defects
281 
282  if missing_defects:
283  log.error("-- The missing defects are used by the following virtual defects")
284  log.error("<missing> : <used by>")
285  for missing in missing_defects:
286  log.error(missing + ":" + ", ".join(used_by[missing]))
287 
288  assert not missing_defects, ("The following defects are used but not "
289  "defined anywhere: %r" % missing_defects)
290  return True
SGTest::store
TestStore store
Definition: TestStore.cxx:23
python.virtual_mixin.DefectsDBVirtualDefectsMixin._virtual_defect_consistency_check
bool _virtual_defect_consistency_check(self)
Definition: virtual_mixin.py:265
python.db.fetch_iovs
def fetch_iovs(folder_name, since=None, until=None, channels=None, tag="", what="all", max_records=-1, with_channel=True, loud=False, database=None, convert_time=False, named_channels=False, selection=None, runs=None, with_time=False, unicode_strings=False)
Definition: DQUtils/python/db.py:65
python.virtual_mixin.DefectsDBVirtualDefectsMixin._get_virtual_defect_logics
Mapping[str, DefectLogic] _get_virtual_defect_logics(self, str tag)
Definition: virtual_mixin.py:232
python.virtual_mixin.DefectsDBVirtualDefectsMixin.update_virtual_defect
None update_virtual_defect(self, str defect_name, str clauses, Optional[str] comment=None)
Definition: virtual_mixin.py:55
python.channel_mapping.get_channel_ids_names
def get_channel_ids_names(folder)
Definition: channel_mapping.py:99
dumpHVPathFromNtuple.append
bool append
Definition: dumpHVPathFromNtuple.py:91
python.virtual_mixin.DefectsDBVirtualDefectsMixin._virtual_defect_logics
_virtual_defect_logics
Definition: virtual_mixin.py:45
python.virtual_logic.DefectLogic
Definition: virtual_logic.py:9
python.exceptions.DefectExistsError
Definition: exceptions.py:3
python.virtual_mixin.DefectsDBVirtualDefectsMixin._virtual_defect_names
_virtual_defect_names
Definition: virtual_mixin.py:101
python.virtual_mixin.DefectsDBVirtualDefectsMixin.validate_clauses
None validate_clauses(self, str clauses)
Definition: virtual_mixin.py:48
python.virtual_mixin.DefectsDBVirtualDefectsMixin.get_intolerable_defects
def get_intolerable_defects(self, bool old_primary_only=True, Sequence[str] exclude=['TIGHT', 'IDTIGHT', 'PHYS_.*'])
Definition: virtual_mixin.py:247
python.virtual_mixin.DefectsDBVirtualDefectsMixin
Definition: virtual_mixin.py:39
python.virtual_mixin.DefectsDBVirtualDefectsMixin._resolve_evaluation_order
List[DefectLogic] _resolve_evaluation_order(self, Optional[Iterable[str]] defects=None)
Definition: virtual_mixin.py:183
python.virtual_mixin.DefectsDBVirtualDefectsMixin.new_virtual_defect
int new_virtual_defect(self, str defect_name, Optional[str] comment, str clauses)
Definition: virtual_mixin.py:148
python.virtual_mixin.DefectsDBVirtualDefectsMixin.resolve_primary_defects
Set[str] resolve_primary_defects(self, Iterable[DefectLogic] defect_logics)
Definition: virtual_mixin.py:208
python.virtual_mixin._resolve_dependencies
def _resolve_dependencies(node, resolved=None, unresolved=None)
Definition: virtual_mixin.py:16
CxxUtils::set
constexpr std::enable_if_t< is_bitmask_v< E >, E & > set(E &lhs, E rhs)
Convenience function to set bits in a class enum bitmask.
Definition: bitmask.h:232
python.virtual_mixin.DefectsDBVirtualDefectsMixin._virtual_initialized
_virtual_initialized
Definition: virtual_mixin.py:104
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
python.virtual_mixin.DefectsDBVirtualDefectsMixin._virtual_defect_id_map
_virtual_defect_id_map
Definition: virtual_mixin.py:102
python.virtual_mixin.DefectsDBVirtualDefectsMixin.virtual_defect_logics
Mapping[str, DefectLogic] virtual_defect_logics(self)
Definition: virtual_mixin.py:220
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:71
python.virtual_mixin.DefectsDBVirtualDefectsMixin._update_virtual_defect
None _update_virtual_defect(self, str defect_name, str clauses, Optional[str] comment=None, Optional[str] tag=None)
Definition: virtual_mixin.py:62
python.virtual_mixin.DefectsDBVirtualDefectsMixin._defect_names
_defect_names
Definition: virtual_mixin.py:107
python.ids.choose_new_defect_id
int choose_new_defect_id(Mapping[Union[str, int], Union[str, int]] existing_map, str defect_name, bool virtual=False)
Definition: ids.py:47
python.virtual_mixin.DefectsDBVirtualDefectsMixin.rename_defect
None rename_defect(self, str defect_name, str new_defect_name)
Definition: virtual_mixin.py:83
python.virtual_mixin.DefectsDBVirtualDefectsMixin._initialized
_initialized
Definition: virtual_mixin.py:109
pickleTool.object
object
Definition: pickleTool.py:29
python.virtual_mixin.DefectsDBVirtualDefectsMixin._defect_id_map
_defect_id_map
Definition: virtual_mixin.py:108
python.virtual_mixin.DefectsDBVirtualDefectsMixin.__init__
None __init__(self)
Definition: virtual_mixin.py:44
python.virtual_mixin.DefectsDBVirtualDefectsMixin.logics_tag
logics_tag
Definition: virtual_mixin.py:58
python.CaloCondLogger.getLogger
def getLogger(name="CaloCond")
Definition: CaloCondLogger.py:16