• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1
2# Copyright 2015 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""MachineImageManager allocates images to duts."""
7
8class MachineImageManager(object):
9  """Management of allocating images to duts.
10
11    * Data structure we have -
12
13      duts_ - list of duts, for each duts, we assume the following 2 properties
14      exist - label_ (the current label the duts_ carries or None, if it has an
15      alien image) and name (a string)
16
17      labels_ - a list of labels, for each label, we assume these properties
18      exist - remote (a set/vector/list of dut names (not dut object), to each
19      of which this image is compatible), remote could be none, which means
20      universal compatible.
21
22      label_duts_ - for each label, we maintain a list of duts, onto which the
23      label is imaged. Note this is an array of lists. Each element of each list
24      is an integer which is dut oridnal. We access this array using label
25      ordinal.
26
27      allocate_log_ - a list of allocation record. For example, if we allocate
28      l1 to d1, then l2 to d2, then allocate_log_ would be [(1, 1), (2, 2)].
29      This is used for debug/log, etc. All tuples in the list are integer pairs
30      (label_ordinal, dut_ordinal).
31
32      n_duts_ - number of duts.
33
34      n_labels_ - number of labels.
35
36      dut_name_ordinal_ - mapping from dut name (a string) to an integer,
37      starting from 0. So that duts_[dut_name_ordinal_[a_dut.name]]= a_dut.
38
39    * Problem abstraction -
40
41      Assume we have the following matrix - label X machine (row X col). A 'X'
42      in (i, j) in the matrix means machine and lable is not compatible, or that
43      we cannot image li to Mj.
44
45           M1   M2   M3
46      L1    X
47
48      L2             X
49
50      L3    X    X
51
52      Now that we'll try to find a way to fill Ys in the matrix so that -
53
54        a) - each row at least get a Y, this ensures that each label get imaged
55        at least once, an apparent prerequiste.
56
57        b) - each column get at most N Ys. This make sure we can successfully
58        finish all tests by re-image each machine at most N times. That being
59        said, we could *OPTIONALLY* reimage some machines more than N times to
60        *accelerate* the test speed.
61
62      How to choose initial N for b) -
63      If number of duts (nd) is equal to or more than that of labels (nl), we
64      start from N == 1. Else we start from N = nl - nd + 1.
65
66      We will begin the search with pre-defined N, if we fail to find such a
67      solution for such N, we increase N by 1 and continue the search till we
68      get N == nl, at this case we fails.
69
70      Such a solution ensures minimal number of reimages.
71
72    * Solution representation
73
74      The solution will be placed inside the matrix, like below
75
76           M1   M2   M3   M4
77      L1    X    X    Y
78
79      L2    Y         X
80
81      L3    X    Y    X
82
83    * Allocation algorithm
84
85      When Mj asks for a image, we check column j, pick the first cell that
86      contains a 'Y', and mark the cell '_'. If no such 'Y' exists (like M4 in
87      the above solution matrix), we just pick an image that the minimal reimage
88      number.
89
90      After allocate for M3
91           M1   M2   M3   M4
92      L1    X    X    _
93
94      L2    Y         X
95
96      L3    X    Y    X
97
98      After allocate for M4
99           M1   M2   M3   M4
100      L1    X    X    _
101
102      L2    Y         X    _
103
104      L3    X    Y    X
105
106      After allocate for M2
107           M1   M2   M3   M4
108      L1    X    X    _
109
110      L2    Y         X    _
111
112      L3    X    _    X
113
114      After allocate for M1
115           M1   M2   M3   M4
116      L1    X    X    _
117
118      L2    _         X    _
119
120      L3    X    _    X
121
122      After allocate for M2
123           M1   M2   M3   M4
124      L1    X    X    _
125
126      L2    _    _    X    _
127
128      L3    X    _    X
129
130      If we try to allocate for M1 or M2 or M3 again, we get None.
131
132    * Special / common case to handle seperately
133
134      We have only 1 dut or if we have only 1 label, that's simple enough.
135
136    """
137
138  def __init__(self, labels, duts):
139    self.labels_ = labels
140    self.duts_ = duts
141    self.n_labels_ = len(labels)
142    self.n_duts_ = len(duts)
143    self.dut_name_ordinal_ = dict()
144    for idx, dut in enumerate(self.duts_):
145      self.dut_name_ordinal_[dut.name] = idx
146
147    # Generate initial matrix containg 'X' or ' '.
148    self.matrix_ = [['X' if (l.remote and len(l.remote)) else ' ' \
149                     for _ in range(self.n_duts_)] for l in self.labels_]
150    for ol, l in enumerate(self.labels_):
151      if l.remote:
152        for r in l.remote:
153          self.matrix_[ol][self.dut_name_ordinal_[r]] = ' '
154
155    self.label_duts_ = [[] for _ in range(self.n_labels_)]
156    self.allocate_log_ = []
157
158  def compute_initial_allocation(self):
159    """Compute the initial label-dut allocation.
160
161        This method finds the most efficient way that every label gets imaged at
162        least once.
163
164        Returns:
165          False, only if not all labels could be imaged to a certain machine,
166          otherwise True.
167        """
168
169    if self.n_duts_ == 1:
170      for i, v in self.matrix_vertical_generator(0):
171        if v != 'X':
172          self.matrix_[i][0] = 'Y'
173      return
174
175    if self.n_labels_ == 1:
176      for j, v in self.matrix_horizontal_generator(0):
177        if v != 'X':
178          self.matrix_[0][j] = 'Y'
179      return
180
181    if self.n_duts_ >= self.n_labels_:
182      n = 1
183    else:
184      n = self.n_labels_ - self.n_duts_ + 1
185    while n <= self.n_labels_:
186      if self._compute_initial_allocation_internal(0, n):
187        break
188      n += 1
189
190    return n <= self.n_labels_
191
192  def _record_allocate_log(self, label_i, dut_j):
193    self.allocate_log_.append((label_i, dut_j))
194    self.label_duts_[label_i].append(dut_j)
195
196  def allocate(self, dut, schedv2=None):
197    """Allocate a label for dut.
198
199        Args:
200          dut: the dut that asks for a new image.
201          schedv2: the scheduling instance, we need the benchmark run
202                   information with schedv2 for a better allocation.
203
204        Returns:
205          a label to image onto the dut or None if no more available images for
206          the dut.
207        """
208    j = self.dut_name_ordinal_[dut.name]
209    # 'can_' prefix means candidate label's.
210    can_reimage_number = 999
211    can_i = 999
212    can_label = None
213    can_pending_br_num = 0
214    for i, v in self.matrix_vertical_generator(j):
215      label = self.labels_[i]
216
217      # 2 optimizations here regarding allocating label to dut.
218      # Note schedv2 might be None in case we do not need this
219      # optimization or we are in testing mode.
220      if schedv2 is not None:
221        pending_br_num = len(schedv2.get_label_map()[label])
222        if pending_br_num == 0:
223          # (A) - we have finished all br of this label,
224          # apparently, we do not want to reimaeg dut to
225          # this label.
226          continue
227      else:
228        # In case we do not have a schedv2 instance, mark
229        # pending_br_num as 0, so pending_br_num >=
230        # can_pending_br_num is always True.
231        pending_br_num = 0
232
233      # For this time being, I just comment this out until we have a
234      # better estimation how long each benchmarkrun takes.
235      # if (pending_br_num <= 5 and
236      #     len(self.label_duts_[i]) >= 1):
237      #     # (B) this is heuristic - if there are just a few test cases
238      #     # (say <5) left undone for this label, and there is at least
239      #     # 1 other machine working on this lable, we probably not want
240      #     # to bother to reimage this dut to help with these 5 test
241      #     # cases
242      #     continue
243
244      if v == 'Y':
245        self.matrix_[i][j] = '_'
246        self._record_allocate_log(i, j)
247        return label
248      if v == ' ':
249        label_reimage_number = len(self.label_duts_[i])
250        if ((can_label is None) or
251            (label_reimage_number < can_reimage_number or
252             (label_reimage_number == can_reimage_number and
253              pending_br_num >= can_pending_br_num))):
254          can_reimage_number = label_reimage_number
255          can_i = i
256          can_label = label
257          can_pending_br_num = pending_br_num
258
259    # All labels are marked either '_' (already taken) or 'X' (not
260    # compatible), so return None to notify machine thread to quit.
261    if can_label is None:
262      return None
263
264    # At this point, we don't find any 'Y' for the machine, so we go the
265    # 'min' approach.
266    self.matrix_[can_i][j] = '_'
267    self._record_allocate_log(can_i, j)
268    return can_label
269
270  def matrix_vertical_generator(self, col):
271    """Iterate matrix vertically at column 'col'.
272
273        Yield row number i and value at matrix_[i][col].
274        """
275    for i, _ in enumerate(self.labels_):
276      yield i, self.matrix_[i][col]
277
278  def matrix_horizontal_generator(self, row):
279    """Iterate matrix horizontally at row 'row'.
280
281        Yield col number j and value at matrix_[row][j].
282        """
283    for j, _ in enumerate(self.duts_):
284      yield j, self.matrix_[row][j]
285
286  def _compute_initial_allocation_internal(self, level, N):
287    """Search matrix for d with N."""
288
289    if level == self.n_labels_:
290      return True
291
292    for j, v in self.matrix_horizontal_generator(level):
293      if v == ' ':
294        # Before we put a 'Y', we check how many Y column 'j' has.
295        # Note y[0] is row idx, y[1] is the cell value.
296        ny = reduce(lambda x, y: x + 1 if (y[1] == 'Y') else x,
297                    self.matrix_vertical_generator(j), 0)
298        if ny < N:
299          self.matrix_[level][j] = 'Y'
300          if self._compute_initial_allocation_internal(level + 1, N):
301            return True
302          self.matrix_[level][j] = ' '
303
304    return False
305