Motr  M0
record.py
Go to the documentation of this file.
1 #
2 # Copyright (c) 2020 Seagate Technology LLC and/or its Affiliates
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16 # For any questions about this software or licensing,
17 # please email opensource@seagate.com or cortx-questions@seagate.com.
18 #
19 
20 """trace class accepts a stream of incoming records (represented by
21  the record class) and produces the output in the form of an SVG image,
22  describing the stream.
23 
24  Some assumptions are made about the input stream:
25 
26  - all records are from the same process,
27 
28  - the time-stamps of incoming records are monotonically
29  increasing. m0addb2dump output does not necessarily conform to this
30  restriction. This can always be fixed by doing
31 
32  m0addb2dump -f | sort -k2.1,2.29 -s | m0addb2dump -d
33 
34  - if a trace does not contain all records produced by a Motr process from
35  start-up to shutdown, certain corner cases (like re-use of the same
36  memory address for a new fom), can result in an incorrect
37  interpretation of a final portion of the trace.
38 
39  Output layout.
40 
41  The entirety of the output image is divided into vertical stripes,
42  corresponding to the localities, divided by 10px-wide vertical lines . The
43  number of localities is specified by the "loc_nr" parameter.
44 
45  Vertical axis corresponds to time (from top to bottom). Horizontal dashed
46  lines mark time-steps with the granularity specified by the "step"
47  parameter.
48 
49  In the area, corresponding to a locality, the foms, executed in this
50  locality are represented. Each fom is represented as a rectangle with fixed
51  width and with the height corresponding to the fom life-time. The left
52  border of this rectangle is marked by a black line, other borders are
53  transparent.
54 
55  The interior of a fom rectangle is divided into 3 vertical "lanes". The
56  first lane is used for labels. When a fom is created, its type and address
57  are written to the label lane. When the fom experiences a phase transition,
58  the name of the new phase is written to the label lane.
59 
60  The second lane, represents phases, marked by different colours.
61 
62  The third lane contains states, marked by different colours.
63 
64  The line at the left border of fom area can be missing if the final state
65  transition for the fom is missing from the log.
66 
67  If fom phase state machine doesn't have transition descriptions, the phase
68  lane will be empty and phase labels will be missing.
69 
70  By specifying "starttime" and "duration" parameters, view area can be
71  narrowed to produce more manageable images. When view area is narrowed, the
72  entire trace is still processed, but the SVG elements that would fall outside
73  of the visible image are omitted.
74 
75 """
76 
77 import datetime
78 import svgwrite
79 
80 class trace(object):
81  def __init__(self, width, height, loc_nr, duration, starttime = None,
82  step = 100, outname = "out.svg", maxfom = 20, verbosity = 0,
83  label = True):
84  self.timeformat = "%Y-%m-%d-%H:%M:%S.%f"
85  if starttime != None:
86  self.start = datetime.datetime.strptime(starttime, self.timeformat)
87  else:
88  self.start = None
89  self.prep = False
90  self.label = label
91  self.width = width
92  self.height = height
93  self.loc_nr = loc_nr
94  self.usec = duration * 1000000
95  self.step = step
96  self.verb = verbosity
97  self.maxfom = maxfom
98  self.out = svgwrite.Drawing(outname, profile='full', \
99  size = (str(width) + "px",
100  str(height) + "px"))
101  self.lmargin = width * 0.01
102  self.reqhwidth = width * 0.87
103  self.lockwidth = width * 0.01
104  self.netwidth = width * 0.05
105  self.iowidth = width * 0.05
106  self.rmargin = width * 0.01
107 
108  assert (self.lmargin + self.reqhwidth + self.lockwidth + self.netwidth +
109  self.iowidth + self.rmargin == self.width)
110 
111  self.reqhstart = self.lmargin
112  self.lockstart = self.reqhstart + self.reqhwidth
113  self.netstart = self.lockstart + self.lockwidth
114  self.iostart = self.netstart + self.netwidth
115 
116  self.loc_width = self.reqhwidth / loc_nr
117  self.loc_margin = self.loc_width * 0.02
118  self.fom_width = (self.loc_width - 2*self.loc_margin) / self.maxfom
119  self.maxlane = 4
120  self.lane_margin = self.fom_width * 0.10
121  self.lane_width = (self.fom_width - 2*self.lane_margin) / self.maxlane
122  self.axis = svgwrite.rgb(0, 0, 0, '%')
123  self.locality = []
124  self.iomax = 128
125  self.iolane = (self.iowidth - 300) / self.iomax
126  self.iolane0 = self.iostart + 300
127  self.iolast = []
128  self.netmax = 128
129  self.netlane = (self.netwidth - 400) / self.netmax
130  self.netlane0 = self.netstart + 400
131  self.netlast = []
132  self.lockmax = 32
133  self.locklane = (self.lockwidth - 300) / self.lockmax
134  self.locklane0 = self.lockstart + 300
135  self.locks = {}
136  self.processed = 0
137  self.reported = 0
138  self.textstep = 15
139  self.scribbles = set()
140  self.foms = {}
141  self.dash = {
142  "stroke" : self.axis,
143  "stroke_width" : 1,
144  "stroke_dasharray" :"1,1"
145  }
146  self.warnedlabel = False
147  self.warnednet = False
148  self.warnedlock = False
149  self.warnedio = False
150  self.warnedfom = 0
151  for i in range(loc_nr):
152  x = self.getloc(i)
153  self.locality.append(locality(self, i))
154  self.line((x, 0), (x, height), stroke = self.axis, stroke_width = 10)
155  self.text("locality " + str(i), insert = (x + 10, 20))
156  for _ in range(self.iomax):
157  self.iolast.append(datetime.datetime(1970, 01, 01))
158  for _ in range(self.netmax):
159  self.netlast.append(datetime.datetime(1970, 01, 01))
160  self.line((self.lockstart - 10, 0), (self.lockstart - 10, height),
161  stroke = self.axis, stroke_width = 10)
162  self.text("lock", insert = (self.lockstart + 10, 20))
163  self.line((self.netstart - 10, 0), (self.netstart - 10, height),
164  stroke = self.axis, stroke_width = 10)
165  self.text("net", insert = (self.netstart + 10, 20))
166  self.line((self.iostart - 10, 0), (self.iostart - 10, height),
167  stroke = self.axis, stroke_width = 10)
168  self.text("io", insert = (self.iostart + 10, 20))
169 
170  def done(self):
171  self.out.save()
172 
173  def fomadd(self, fom):
174  self.locality[fom.getloc()].fomadd(fom)
175 
176  def fomdel(self, fom):
177  self.locality[fom.getloc()].fomdel(fom)
178 
179  def getloc(self, idx):
180  return self.reqhstart + self.loc_width * idx
181 
182  def getlane(self, fom, lane):
183  assert 0 <= lane and lane < self.maxlane
184  return self.getloc(fom.loc.idx) + self.loc_margin + \
185  self.fom_width * fom.loc_idx + self.lane_margin + \
186  self.lane_width * lane
187 
188  def getpos(self, stamp):
189  interval = stamp - self.start
190  usec = interval.microseconds + (interval.seconds +
191  interval.days * 24 * 3600) * 1000000
192  return self.height * usec / self.usec
193 
194  def fomfind(self, rec):
195  addr = rec.get("fom")
196  f = self.foms.get(addr)
197  if f == None:
198  f = fom()
199  f.time = self.start
200  f.params = [None, None, None, None, None, rec.ctx["fom"][1]]
201  f.ctx = rec.ctx
202  f.done(self)
203  return f
204 
205  def getcolour(self, str):
206  seed = str + "^" + str
207  red = hash(seed + "r") % 90
208  green = hash(seed + "g") % 90
209  blue = hash(seed + "b") % 90
210  return svgwrite.rgb(red, green, blue, '%')
211 
212  def fomcolour(self, fom):
213  return self.getcolour(fom.phase)
214 
215  def fomrect(self, fom, lane, start, end):
216  start = self.getpos(start)
217  height = self.getpos(end) - start
218  lane = self.getlane(fom, lane)
219  return { "insert": (lane, start), "size": (self.lane_width, height) }
220 
221  def statecolour(self, fom):
222  state = fom.state
223  if state == "Init":
224  return svgwrite.rgb(100, 100, 0, '%')
225  elif state == "Ready":
226  return svgwrite.rgb(100, 0, 0, '%')
227  elif state == "Running":
228  return svgwrite.rgb(0, 100, 0, '%')
229  elif state == "Waiting":
230  return svgwrite.rgb(0, 0, 100, '%')
231  else:
232  return svgwrite.rgb(10, 10, 10, '%')
233 
234  def rect(self, **kw):
235  y = kw["insert"][1]
236  h = kw["size"][1]
237  if y + h >= 0 and y < self.height:
238  self.out.add(self.out.rect(**kw))
239 
240  def line(self, start, end, **kw):
241  if end[1] >= 0 and start[1] < self.height:
242  self.out.add(self.out.line(start, end, **kw))
243 
244  def tline(self, start, end, **kw):
245  if self.label:
246  self.line(start, end, **kw)
247 
248  def text(self, text, connect = False, force = False, **kw):
249  x = int(kw["insert"][0])
250  y0 = y = int(kw["insert"][1]) // self.textstep * self.textstep
251  if not self.label and not force:
252  return (x, y)
253  if y >= 0 and y < self.height:
254  i = 0
255  while (x, y // self.textstep) in self.scribbles:
256  y += self.textstep
257  if i > 30:
258  if not self.warnedlabel:
259  print "Labels are overcrowded. Increase image height."
260  self.warnedlabel = True
261  break
262  i += 1
263  kw["insert"] = (x + 10, y)
264  kw["font_family"] = "Courier"
265  self.out.add(self.out.text(text, **kw))
266  if connect:
267  self.line((x, y0), (x + 10, y - 4), **self.dash)
268  self.scribbles.add((x, y // self.textstep))
269  return x + 10 + len(text) * 9.5, y - 4 # magic. Depends on the font.
270 
271  def fomtext(self, fom, text, time):
272  return self.text(text, insert = (self.getlane(fom, 0),
273  self.getpos(time)))
274 
275  def prepare(self, time):
276  if self.start == None:
277  self.start = time
278  self.lastreport = self.start
279  duration = datetime.timedelta(microseconds = self.usec)
280  self.end = self.start + duration
281  delta = datetime.timedelta(milliseconds = self.step)
282  n = 0
283  while n*delta <= duration:
284  t = self.start + n * delta
285  y = self.getpos(t)
286  label = t.strftime(self.timeformat)
287  self.line((0, y), (self.width, y), stroke = self.axis,
288  stroke_width = 1, stroke_dasharray = "20,10,5,5,5,10")
289  self.text(label, insert = (0, y - 10), force = True)
290  n = n + 1
291  self.prep = True
292 
293  def ioadd(self, time, fid, seconds):
294  duration = datetime.timedelta(microseconds = float(seconds) * 1000000)
295  start = time - duration
296  y0 = self.getpos(start)
297  y1 = self.getpos(time)
298  l0 = self.text("L " + fid, insert = (self.iostart, y0))
299  l1 = self.text("E " + fid, insert = (self.iostart, y1))
300  slot = next((i for i in range(len(self.iolast)) if
301  self.iolast[i] < start), None)
302  if slot != None:
303  x = self.iolane0 + self.iolane * slot
304  self.rect(insert = (x, y0), size = (self.iolane * 3/4, y1 - y0),
305  fill = self.getcolour(str(slot) + str(start)))
306  self.iolast[slot] = time
307  self.tline(l0, (x, y0), **self.dash)
308  self.tline(l1, (x, y1), **self.dash)
309  elif not self.warnedio:
310  self.warnedio = True
311  print "Too many concurrent IO-s. Increase iomax."
312 
313  def netbufadd(self, time, buf, qtype, seconds, stime, status, length):
314  qname = [
315  "msg-recv",
316  "msg-send",
317  "p-bulk-recv",
318  "p-bulk-send",
319  "a-bulk-recv",
320  "a-bulk-send",
321  ]
322  if qtype == 0:
323  return # skip receives
324  start = parsetime(stime)
325  duration = datetime.timedelta(microseconds = float(seconds) * 1000000)
326  dequeue = start + duration
327  assert start <= dequeue and dequeue <= time
328  y0 = self.getpos(start)
329  y1 = self.getpos(dequeue)
330  y2 = self.getpos(time)
331  l0 = self.text("Q " + buf + " " + qname[qtype] + " " + str(length),
332  insert = (self.netstart, y0))
333  l2 = self.text("C " + buf, insert = (self.netstart, y2))
334  slot = next((i for i in range(len(self.netlast)) if
335  self.netlast[i] < start), None)
336  if slot != None:
337  x = self.netlane0 + self.netlane * slot
338  self.rect(insert = (x, y0), size = (self.netlane * 3/4, y1 - y0),
339  fill = self.getcolour(qname[qtype]))
340  self.rect(insert = (x, y1), size = (self.netlane * 1/4, y2 - y1),
341  fill = self.getcolour("cb"))
342  self.netlast[slot] = time
343  self.tline(l0, (x, y0), **self.dash)
344  self.tline(l2, (x, y2), **self.dash)
345  elif not self.warnednet:
346  self.warnednet = True
347  print "Too many concurrent netbufs. Increase netmax."
348 
349  def mutex(self, mname, label, time, seconds, addr):
350  duration = datetime.timedelta(microseconds = float(seconds) * 1000000)
351  start = time - duration
352  y0 = self.getpos(start)
353  y1 = self.getpos(time)
354  exists = addr in self.locks
355  if not exists:
356  if len(self.locks) >= self.lockmax:
357  if not self.warnedlock:
358  self.warnedlock = True
359  print "Too many locks. Increase lockmax."
360  return
361  self.locks[addr] = len(self.locks)
362  lane = self.locks[addr]
363  x = self.locklane0 + self.locklane * lane
364  if not exists:
365  ly = max(y0, 40)
366  self.tline((x, 0), (x, self.height), **self.dash)
367  l = self.text(mname + " " + str(addr), insert = (self.lockstart, ly))
368  self.tline(l, (x, ly), **self.dash)
369  self.rect(insert = (x, y0), size = (self.locklane * 3/4, y1 - y0),
370  fill = self.getcolour(label), stroke = self.axis)
371 
373  def __init__(self, trace, idx):
374  self.trace = trace
375  self.foms = {}
376  self.idx = idx
377 
378  def fomadd(self, fom):
379  trace = self.trace
380  j = len(self.foms)
381  for i in range(len(self.foms)):
382  if i not in self.foms:
383  j = i
384  break
385  if j > trace.maxfom:
386  if trace.warnedfom < j:
387  print ("{}: too many concurrent foms, "
388  "increase maxfom to {}".format(fom.time, j))
389  trace.warnedfom = j
390  self.foms[j] = fom
391  fom.loc_idx = j
392  fom.loc = self
393 
394  def fomdel(self, fom):
395  assert self.foms[fom.loc_idx] == fom
396  del self.foms[fom.loc_idx]
397 
398 def keep(word):
399  return word in tags
400 
401 def parsetime(stamp):
402  #
403  # strptime() is expensive. Hard-code.
404  # # cut to microsecond precision
405  # datetime.datetime.strptime(stamp[0:-3], trace.timeformat)
406  #
407  # 2016-03-24-09:18:46.359427942
408  # 01234567890123456789012345678
409  return datetime.datetime(year = int(stamp[ 0: 4]),
410  month = int(stamp[ 5: 7]),
411  day = int(stamp[ 8:10]),
412  hour = int(stamp[11:13]),
413  minute = int(stamp[14:16]),
414  second = int(stamp[17:19]),
415  microsecond = int(stamp[20:26]))
416 
417 def parse(trace, words):
418  stamp = words[0]
419  tag = words[1]
420  if tag in tags:
421  obj = tags[tag]()
422  obj.ctx = {}
423  obj.time = parsetime(stamp)
424  obj.params = words[2:]
425  obj.trace = trace
426  if not trace.prep:
427  trace.prepare(obj.time)
428  else:
429  obj = None
430  return obj
431 
432 class record(object):
433  def add(self, words):
434  key = words[0]
435  val = words[1:]
436  assert key not in self.ctx
437  self.ctx[key] = val
438  self.trace = None
439 
440  def done(self, trace):
441  self.trace = trace
442  trace.processed = trace.processed + 1
443  if (trace.verb > 0 and
444  self.time - trace.lastreport > datetime.timedelta(seconds = 1)):
445  print self.time, trace.processed - trace.reported, trace.processed
446  trace.lastreport = self.time
447  trace.reported = trace.processed
448 
449  def get(self, label):
450  return self.ctx[label][0]
451 
452  def fomexists(self):
453  return "fom" in self.ctx
454 
455  def __str__(self):
456  return str(self.time)
457 
458  def getloc(self):
459  loc = int(self.get("locality"))
460  if self.trace.loc_nr == 1:
461  loc = 0
462  assert 0 <= loc and loc < self.trace.loc_nr
463  return loc
464 
465 class fstate(record):
466  def done(self, trace):
467  state = self.params[2]
468  super(fstate, self).done(trace)
469  if self.fomexists():
470  fom = trace.fomfind(self)
471  trace.rect(fill = trace.statecolour(fom),
472  **trace.fomrect(fom, 3, fom.state_time, self.time))
473  fom.state_time = self.time
474  fom.state = state
475  if state == "Finished":
476  start = trace.getpos(fom.time)
477  end = trace.getpos(self.time)
478  lane = trace.getlane(fom, 0) - 5
479  trace.line((lane, start), (lane, end), stroke = trace.axis,
480  stroke_width = 3)
481  self.trace.fomdel(fom)
482  del trace.foms[self.get("fom")]
483 
484 
485 class fphase(record):
486  def done(self, trace):
487  super(fphase, self).done(trace)
488  if (len(self.params) in (2, 3) and self.fomexists()):
489  fom = trace.fomfind(self)
490  trace.rect(fill = trace.fomcolour(fom),
491  **trace.fomrect(fom, 2, fom.phase_time, self.time))
492  l = trace.fomtext(fom, fom.phase, fom.phase_time)
493  x = trace.getlane(fom, 1)
494  if l[0] < x:
495  trace.tline(l, (x, l[1]), **trace.dash)
496  trace.tline((x, l[1]),
497  (trace.getlane(fom, 2), trace.getpos(fom.phase_time)),
498  **trace.dash)
499  fom.phase_time = self.time
500  fom.phase = self.params[-1]
501 
502 class fom(record):
503  def done(self, trace):
504  addr = self.get("fom")
505  assert "locality" in self.ctx
506  # assert addr not in trace.foms
507  trace.foms[addr] = self
508  super(fom, self).done(trace)
509  self.loc_idx = -1
510  self.trace.fomadd(self)
511  self.state = "Ready"
512  self.phase = "init"
513  self.state_time = self.time
514  self.phase_time = self.time
515  trace.fomtext(self, self.params[5] + str(addr) +
516  "[" + self.get("locality") + "]", self.time)
517 
518  def __str__(self):
519  return str(self.time) + " " + self.get("fom")
520 
521 class forq(record):
522  def done(self, trace):
523  if "locality" not in self.ctx:
524  return # ast in 0-locality
525  super(forq, self).done(trace)
526  loc_id = self.getloc()
527  nanoseconds = float(self.params[0][:-1]) # Cut final comma.
528  duration = datetime.timedelta(microseconds = nanoseconds / 1000)
529  x = self.trace.getloc(loc_id) + 10
530  y = self.trace.getpos(self.time - duration)
531  trace.tline((x, y), (x, self.trace.getpos(self.time)),
532  stroke = svgwrite.rgb(80, 10, 10, '%'), stroke_width = 5)
533  trace.text(self.params[1], connect = True, insert = (x + 10, y))
534 
535 class ioend(record):
536  def done(self, trace):
537  super(ioend, self).done(trace)
538  trace.ioadd(self.time, self.params[0][:-1], self.params[2][:-1])
539 
540 class netbuf(record):
541  def done(self, trace):
542  super(netbuf, self).done(trace)
543  assert (self.params[0] == "buf:" and self.params[2] == "qtype:" and
544  self.params[4] == "time:" and self.params[6] == "duration:"
545  and self.params[8] == "status:" and self.params[10] == "len:")
546  trace.netbufadd(self.time,
547  buf = self.params[1][:-1],
548  qtype = int(self.params[3][:-1]),
549  stime = self.params[5][:-1],
550  seconds = float(self.params[7][:-1]),
551  status = int(self.params[9][:-1]),
552  length = int(self.params[11])) # no comma: last one
553 
554 class mutex(record):
555  def setname(self, mname, label):
556  self.mname = mname
557  self.label = label
558 
559  def done(self, trace):
560  super(mutex, self).done(trace)
561  trace.mutex(self.mname, self.label, self.time,
562  float(self.params[0][:-1]), self.params[1])
563 
565  def done(self, trace):
566  self.setname("rpc-mach", "wait")
567  super(rpcmachwait, self).done(trace)
568 
570  def done(self, trace):
571  self.setname("rpc-mach", "hold")
572  super(rpcmachhold, self).done(trace)
573 
574 tags = {
575  "fom-descr" : fom,
576  "fom-state" : fstate,
577  "fom-phase" : fphase,
578  "loc-forq-duration" : forq,
579  "stob-io-end" : ioend,
580  "net-buf" : netbuf,
581  "rpc-mach-wait" : rpcmachwait,
582  "rpc-mach-hold" : rpcmachhold
583 }
def fomdel(self, fom)
Definition: record.py:394
def done(self, trace)
Definition: record.py:440
def fomadd(self, fom)
Definition: record.py:378
def fomadd(self, fom)
Definition: record.py:173
def getpos(self, stamp)
Definition: record.py:188
def getloc(self)
Definition: record.py:458
def fomcolour(self, fom)
Definition: record.py:212
def __init__(self, trace, idx)
Definition: record.py:373
def done(self, trace)
Definition: record.py:559
def fomdel(self, fom)
Definition: record.py:176
def mutex(self, mname, label, time, seconds, addr)
Definition: record.py:349
def netbufadd(self, time, buf, qtype, seconds, stime, status, length)
Definition: record.py:313
def done(self, trace)
Definition: record.py:486
def line(self, start, end, kw)
Definition: record.py:240
def fomexists(self)
Definition: record.py:452
def parse(trace, words)
Definition: record.py:417
def fomtext(self, fom, text, time)
Definition: record.py:271
def done(self, trace)
Definition: record.py:570
def done(self, trace)
Definition: record.py:503
def done(self, trace)
Definition: record.py:541
def fomfind(self, rec)
Definition: record.py:194
def __init__(self, width, height, loc_nr, duration, starttime=None, step=100, outname="out.svg", maxfom=20, verbosity=0, label=True)
Definition: record.py:83
static int get(struct scanner *s, void *buf, size_t nob)
Definition: beck.c:1253
def tline(self, start, end, kw)
Definition: record.py:244
def keep(word)
Definition: record.py:398
static long long max(long long a, long long b)
Definition: crate.c:196
static int next[]
Definition: cp.c:248
def getlane(self, fom, lane)
Definition: record.py:182
def done(self, trace)
Definition: record.py:536
def get(self, label)
Definition: record.py:449
def getloc(self, idx)
Definition: record.py:179
def setname(self, mname, label)
Definition: record.py:555
def parsetime(stamp)
Definition: record.py:401
def done(self, trace)
Definition: record.py:466
def done(self, trace)
Definition: record.py:565
def fomrect(self, fom, lane, start, end)
Definition: record.py:215
def done(self, trace)
Definition: record.py:522
format
Definition: hist.py:128
def add(self, words)
Definition: record.py:433
def text(self, text, connect=False, force=False, kw)
Definition: record.py:248
def ioadd(self, time, fid, seconds)
Definition: record.py:293
def rect(self, kw)
Definition: record.py:234
def done(self)
Definition: record.py:170
def getcolour(self, str)
Definition: record.py:205
def __str__(self)
Definition: record.py:455
def __str__(self)
Definition: record.py:518
def prepare(self, time)
Definition: record.py:275
def statecolour(self, fom)
Definition: record.py:221
static void add(struct m0_addb2_mach *mach, uint64_t id, int n, const uint64_t *value)
Definition: addb2.c:934