aboutsummaryrefslogtreecommitdiffstats
path: root/tools/perf/scripts
diff options
context:
space:
mode:
authorFrederic Weisbecker <fweisbec@gmail.com>2010-07-20 15:55:33 -0400
committerFrederic Weisbecker <fweisbec@gmail.com>2010-08-01 19:31:41 -0400
commit880d22f2470af6037715b7f6eb083b6ec5561d92 (patch)
tree5792f419a5e91bac4e259fb0df3eb2ffbadbb45a /tools/perf/scripts
parent819ce45afebd77a9de736fa5304ba8352d11dff9 (diff)
perf: New migration tool overview
This brings a GUI tool that displays an overview of the load of tasks proportion in each CPUs. The CPUs forward progress is cut in timeslices. A new timeslice is created for every runqueue event: a task gets pushed out or pulled in the runqueue. For each timeslice, every CPUs rectangle is colored with a red power that describes the local load against the total load. This more red is the rectangle, the higher is the given CPU load. This load is the number of tasks running on the CPU, without any distinction against the scheduler policy of the tasks, for now. Also for each timeslice, the event origin is depicted on the CPUs that triggered it using a thin colored line on top of the rectangle timeslice. These events are: * sleep: a task went to sleep and has then been pulled out the runqueue. The origin color in the thin line is dark blue. * wake up: a task woke up and has then been pushed in the runqueue. The origin color is yellow. * wake up new: a new task woke up and has then been pushed in the runqueue. The origin color is green. * migrate in: a task migrated in the runqueue due to a load balancing operation. The origin color is violet. * migrate out: reverse of the previous one. Migrate in events usually have paired migrate out events in another runqueue. The origin color is light blue. Clicking on a timeslice provides the runqueue event details and the runqueue state. The CPU rectangles can be navigated using the usual arrow controls. Horizontal zooming in/out is possible with the "+" and "-" buttons. Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com> Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: Li Zefan <lizf@cn.fujitsu.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Tom Zanussi <tzanussi@gmail.com> Cc: Mike Galbraith <efault@gmx.de> Cc: Venkatesh Pallipadi <venki@google.com> Cc: Pierre Tardy <tardyp@gmail.com> Cc: Nikhil Rao <ncrao@google.com> Cc: Li Zefan <lizf@cn.fujitsu.com>
Diffstat (limited to 'tools/perf/scripts')
-rw-r--r--tools/perf/scripts/python/bin/sched-migration-record2
-rw-r--r--tools/perf/scripts/python/bin/sched-migration-report3
-rw-r--r--tools/perf/scripts/python/sched-migration.py634
3 files changed, 639 insertions, 0 deletions
diff --git a/tools/perf/scripts/python/bin/sched-migration-record b/tools/perf/scripts/python/bin/sched-migration-record
new file mode 100644
index 000000000000..17a3e9bd9e8f
--- /dev/null
+++ b/tools/perf/scripts/python/bin/sched-migration-record
@@ -0,0 +1,2 @@
1#!/bin/bash
2perf record -m 16384 -a -e sched:sched_wakeup -e sched:sched_wakeup_new -e sched:sched_switch -e sched:sched_migrate_task $@
diff --git a/tools/perf/scripts/python/bin/sched-migration-report b/tools/perf/scripts/python/bin/sched-migration-report
new file mode 100644
index 000000000000..61d05f72e443
--- /dev/null
+++ b/tools/perf/scripts/python/bin/sched-migration-report
@@ -0,0 +1,3 @@
1#!/bin/bash
2# description: sched migration overview
3perf trace $@ -s ~/libexec/perf-core/scripts/python/sched-migration.py
diff --git a/tools/perf/scripts/python/sched-migration.py b/tools/perf/scripts/python/sched-migration.py
new file mode 100644
index 000000000000..f73e1c736a34
--- /dev/null
+++ b/tools/perf/scripts/python/sched-migration.py
@@ -0,0 +1,634 @@
1#!/usr/bin/python
2#
3# Cpu task migration overview toy
4#
5# Copyright (C) 2010 Frederic Weisbecker <fweisbec@gmail.com>
6#
7# perf trace event handlers have been generated by perf trace -g python
8#
9# The whole is licensed under the terms of the GNU GPL License version 2
10
11
12try:
13 import wx
14except ImportError:
15 raise ImportError, "You need to install the wxpython lib for this script"
16
17import os
18import sys
19
20from collections import defaultdict
21from UserList import UserList
22
23sys.path.append(os.environ['PERF_EXEC_PATH'] + \
24 '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
25
26from perf_trace_context import *
27from Core import *
28
29class RootFrame(wx.Frame):
30 def __init__(self, timeslices, parent = None, id = -1, title = "Migration"):
31 wx.Frame.__init__(self, parent, id, title)
32
33 (self.screen_width, self.screen_height) = wx.GetDisplaySize()
34 self.screen_width -= 10
35 self.screen_height -= 10
36 self.zoom = 0.5
37 self.scroll_scale = 20
38 self.timeslices = timeslices
39 (self.ts_start, self.ts_end) = timeslices.interval()
40 self.update_width_virtual()
41
42 # whole window panel
43 self.panel = wx.Panel(self, size=(self.screen_width, self.screen_height))
44
45 # scrollable container
46 self.scroll = wx.ScrolledWindow(self.panel)
47 self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, 100 / 10)
48 self.scroll.EnableScrolling(True, True)
49 self.scroll.SetFocus()
50
51 # scrollable drawing area
52 self.scroll_panel = wx.Panel(self.scroll, size=(self.screen_width, self.screen_height / 2))
53 self.scroll_panel.Bind(wx.EVT_PAINT, self.on_paint)
54 self.scroll_panel.Bind(wx.EVT_KEY_DOWN, self.on_key_press)
55 self.scroll_panel.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
56 self.scroll.Bind(wx.EVT_PAINT, self.on_paint)
57
58 self.scroll.Fit()
59 self.Fit()
60
61 self.scroll_panel.SetDimensions(-1, -1, self.width_virtual, -1, wx.SIZE_USE_EXISTING)
62
63 self.max_cpu = -1
64 self.txt = None
65
66 self.Show(True)
67
68 def us_to_px(self, val):
69 return val / (10 ** 3) * self.zoom
70
71 def px_to_us(self, val):
72 return (val / self.zoom) * (10 ** 3)
73
74 def scroll_start(self):
75 (x, y) = self.scroll.GetViewStart()
76 return (x * self.scroll_scale, y * self.scroll_scale)
77
78 def scroll_start_us(self):
79 (x, y) = self.scroll_start()
80 return self.px_to_us(x)
81
82 def update_rectangle_cpu(self, dc, slice, cpu, offset_time):
83 rq = slice.rqs[cpu]
84
85 if slice.total_load != 0:
86 load_rate = rq.load() / float(slice.total_load)
87 else:
88 load_rate = 0
89
90
91 offset_px = self.us_to_px(slice.start - offset_time)
92 width_px = self.us_to_px(slice.end - slice.start)
93 (x, y) = self.scroll_start()
94
95 if width_px == 0:
96 return
97
98 offset_py = 100 + (cpu * 150)
99 width_py = 100
100
101 if cpu in slice.event_cpus:
102 rgb = rq.event.color()
103 if rgb is not None:
104 (r, g, b) = rgb
105 color = wx.Colour(r, g, b)
106 brush = wx.Brush(color, wx.SOLID)
107 dc.SetBrush(brush)
108 dc.DrawRectangle(offset_px, offset_py, width_px, 5)
109 width_py -= 5
110 offset_py += 5
111
112 red_power = int(0xff - (0xff * load_rate))
113 color = wx.Colour(0xff, red_power, red_power)
114 brush = wx.Brush(color, wx.SOLID)
115 dc.SetBrush(brush)
116 dc.DrawRectangle(offset_px, offset_py, width_px, width_py)
117
118 def update_rectangles(self, dc, start, end):
119 if len(self.timeslices) == 0:
120 return
121 start += self.timeslices[0].start
122 end += self.timeslices[0].start
123
124 color = wx.Colour(0, 0, 0)
125 brush = wx.Brush(color, wx.SOLID)
126 dc.SetBrush(brush)
127
128 i = self.timeslices.find_time_slice(start)
129 if i == -1:
130 return
131
132 for i in xrange(i, len(self.timeslices)):
133 timeslice = self.timeslices[i]
134 if timeslice.start > end:
135 return
136
137 for cpu in timeslice.rqs:
138 self.update_rectangle_cpu(dc, timeslice, cpu, self.timeslices[0].start)
139 if cpu > self.max_cpu:
140 self.max_cpu = cpu
141
142 def on_paint(self, event):
143 color = wx.Colour(0xff, 0xff, 0xff)
144 brush = wx.Brush(color, wx.SOLID)
145 dc = wx.PaintDC(self.scroll_panel)
146 dc.SetBrush(brush)
147
148 width = min(self.width_virtual, self.screen_width)
149 (x, y) = self.scroll_start()
150 start = self.px_to_us(x)
151 end = self.px_to_us(x + width)
152 self.update_rectangles(dc, start, end)
153
154 def cpu_from_ypixel(self, y):
155 y -= 100
156 cpu = y / 150
157 height = y % 150
158
159 if cpu < 0 or cpu > self.max_cpu or height > 100:
160 return -1
161
162 return cpu
163
164 def update_summary(self, cpu, t):
165 idx = self.timeslices.find_time_slice(t)
166 if idx == -1:
167 return
168
169 ts = self.timeslices[idx]
170 rq = ts.rqs[cpu]
171 raw = "CPU: %d\n" % cpu
172 raw += "Last event : %s\n" % rq.event.__repr__()
173 raw += "Timestamp : %d.%06d\n" % (ts.start / (10 ** 9), (ts.start % (10 ** 9)) / 1000)
174 raw += "Duration : %6d us\n" % ((ts.end - ts.start) / (10 ** 6))
175 raw += "Load = %d\n" % rq.load()
176 for t in rq.tasks:
177 raw += "%s \n" % thread_name(t)
178
179 if self.txt:
180 self.txt.Destroy()
181 self.txt = wx.StaticText(self.panel, -1, raw, (0, (self.screen_height / 2) + 50))
182
183
184 def on_mouse_down(self, event):
185 (x, y) = event.GetPositionTuple()
186 cpu = self.cpu_from_ypixel(y)
187 if cpu == -1:
188 return
189
190 t = self.px_to_us(x) + self.timeslices[0].start
191
192 self.update_summary(cpu, t)
193
194
195 def update_width_virtual(self):
196 self.width_virtual = self.us_to_px(self.ts_end - self.ts_start)
197
198 def __zoom(self, x):
199 self.update_width_virtual()
200 (xpos, ypos) = self.scroll.GetViewStart()
201 xpos = self.us_to_px(x) / self.scroll_scale
202 self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, 100 / 10, xpos, ypos)
203 self.Refresh()
204
205 def zoom_in(self):
206 x = self.scroll_start_us()
207 self.zoom *= 2
208 self.__zoom(x)
209
210 def zoom_out(self):
211 x = self.scroll_start_us()
212 self.zoom /= 2
213 self.__zoom(x)
214
215
216 def on_key_press(self, event):
217 key = event.GetRawKeyCode()
218 if key == ord("+"):
219 self.zoom_in()
220 return
221 if key == ord("-"):
222 self.zoom_out()
223 return
224
225 key = event.GetKeyCode()
226 (x, y) = self.scroll.GetViewStart()
227 if key == wx.WXK_RIGHT:
228 self.scroll.Scroll(x + 1, y)
229 elif key == wx.WXK_LEFT:
230 self.scroll.Scroll(x -1, y)
231
232
233threads = { 0 : "idle"}
234
235def thread_name(pid):
236 return "%s:%d" % (threads[pid], pid)
237
238class EventHeaders:
239 def __init__(self, common_cpu, common_secs, common_nsecs,
240 common_pid, common_comm):
241 self.cpu = common_cpu
242 self.secs = common_secs
243 self.nsecs = common_nsecs
244 self.pid = common_pid
245 self.comm = common_comm
246
247 def ts(self):
248 return (self.secs * (10 ** 9)) + self.nsecs
249
250 def ts_format(self):
251 return "%d.%d" % (self.secs, int(self.nsecs / 1000))
252
253
254def taskState(state):
255 states = {
256 0 : "R",
257 1 : "S",
258 2 : "D",
259 64: "DEAD"
260 }
261
262 if state not in states:
263 print "Unhandled task state %d" % state
264 return ""
265
266 return states[state]
267
268
269class RunqueueEventUnknown:
270 @staticmethod
271 def color():
272 return None
273
274 def __repr__(self):
275 return "unknown"
276
277class RunqueueEventSleep:
278 @staticmethod
279 def color():
280 return (0, 0, 0xff)
281
282 def __init__(self, sleeper):
283 self.sleeper = sleeper
284
285 def __repr__(self):
286 return "%s gone to sleep" % thread_name(self.sleeper)
287
288class RunqueueEventWakeup:
289 @staticmethod
290 def color():
291 return (0xff, 0xff, 0)
292
293 def __init__(self, wakee):
294 self.wakee = wakee
295
296 def __repr__(self):
297 return "%s woke up" % thread_name(self.wakee)
298
299class RunqueueEventFork:
300 @staticmethod
301 def color():
302 return (0, 0xff, 0)
303
304 def __init__(self, child):
305 self.child = child
306
307 def __repr__(self):
308 return "new forked task %s" % thread_name(self.child)
309
310class RunqueueMigrateIn:
311 @staticmethod
312 def color():
313 return (0, 0xf0, 0xff)
314
315 def __init__(self, new):
316 self.new = new
317
318 def __repr__(self):
319 return "task migrated in %s" % thread_name(self.new)
320
321class RunqueueMigrateOut:
322 @staticmethod
323 def color():
324 return (0xff, 0, 0xff)
325
326 def __init__(self, old):
327 self.old = old
328
329 def __repr__(self):
330 return "task migrated out %s" % thread_name(self.old)
331
332class RunqueueSnapshot:
333 def __init__(self, tasks = [0], event = RunqueueEventUnknown()):
334 self.tasks = tuple(tasks)
335 self.event = event
336
337 def sched_switch(self, prev, prev_state, next):
338 event = RunqueueEventUnknown()
339
340 if taskState(prev_state) == "R" and next in self.tasks \
341 and prev in self.tasks:
342 return self
343
344 if taskState(prev_state) != "R":
345 event = RunqueueEventSleep(prev)
346
347 next_tasks = list(self.tasks[:])
348 if prev in self.tasks:
349 if taskState(prev_state) != "R":
350 next_tasks.remove(prev)
351 elif taskState(prev_state) == "R":
352 next_tasks.append(prev)
353
354 if next not in next_tasks:
355 next_tasks.append(next)
356
357 return RunqueueSnapshot(next_tasks, event)
358
359 def migrate_out(self, old):
360 if old not in self.tasks:
361 return self
362 next_tasks = [task for task in self.tasks if task != old]
363
364 return RunqueueSnapshot(next_tasks, RunqueueMigrateOut(old))
365
366 def __migrate_in(self, new, event):
367 if new in self.tasks:
368 self.event = event
369 return self
370 next_tasks = self.tasks[:] + tuple([new])
371
372 return RunqueueSnapshot(next_tasks, event)
373
374 def migrate_in(self, new):
375 return self.__migrate_in(new, RunqueueMigrateIn(new))
376
377 def wake_up(self, new):
378 return self.__migrate_in(new, RunqueueEventWakeup(new))
379
380 def wake_up_new(self, new):
381 return self.__migrate_in(new, RunqueueEventFork(new))
382
383 def load(self):
384 """ Provide the number of tasks on the runqueue.
385 Don't count idle"""
386 return len(self.tasks) - 1
387
388 def __repr__(self):
389 ret = self.tasks.__repr__()
390 ret += self.origin_tostring()
391
392 return ret
393
394class TimeSlice:
395 def __init__(self, start, prev):
396 self.start = start
397 self.prev = prev
398 self.end = start
399 # cpus that triggered the event
400 self.event_cpus = []
401 if prev is not None:
402 self.total_load = prev.total_load
403 self.rqs = prev.rqs.copy()
404 else:
405 self.rqs = defaultdict(RunqueueSnapshot)
406 self.total_load = 0
407
408 def __update_total_load(self, old_rq, new_rq):
409 diff = new_rq.load() - old_rq.load()
410 self.total_load += diff
411
412 def sched_switch(self, ts_list, prev, prev_state, next, cpu):
413 old_rq = self.prev.rqs[cpu]
414 new_rq = old_rq.sched_switch(prev, prev_state, next)
415
416 if old_rq is new_rq:
417 return
418
419 self.rqs[cpu] = new_rq
420 self.__update_total_load(old_rq, new_rq)
421 ts_list.append(self)
422 self.event_cpus = [cpu]
423
424 def migrate(self, ts_list, new, old_cpu, new_cpu):
425 if old_cpu == new_cpu:
426 return
427 old_rq = self.prev.rqs[old_cpu]
428 out_rq = old_rq.migrate_out(new)
429 self.rqs[old_cpu] = out_rq
430 self.__update_total_load(old_rq, out_rq)
431
432 new_rq = self.prev.rqs[new_cpu]
433 in_rq = new_rq.migrate_in(new)
434 self.rqs[new_cpu] = in_rq
435 self.__update_total_load(new_rq, in_rq)
436
437 ts_list.append(self)
438 self.event_cpus = [old_cpu, new_cpu]
439
440 def wake_up(self, ts_list, pid, cpu, fork):
441 old_rq = self.prev.rqs[cpu]
442 if fork:
443 new_rq = old_rq.wake_up_new(pid)
444 else:
445 new_rq = old_rq.wake_up(pid)
446
447 if new_rq is old_rq:
448 return
449 self.rqs[cpu] = new_rq
450 self.__update_total_load(old_rq, new_rq)
451 ts_list.append(self)
452 self.event_cpus = [cpu]
453
454 def next(self, t):
455 self.end = t
456 return TimeSlice(t, self)
457
458class TimeSliceList(UserList):
459 def __init__(self, arg = []):
460 self.data = arg
461
462 def get_time_slice(self, ts):
463 if len(self.data) == 0:
464 slice = TimeSlice(ts, TimeSlice(-1, None))
465 else:
466 slice = self.data[-1].next(ts)
467 return slice
468
469 def find_time_slice(self, ts):
470 start = 0
471 end = len(self.data)
472 found = -1
473 searching = True
474 while searching:
475 if start == end or start == end - 1:
476 searching = False
477
478 i = (end + start) / 2
479 if self.data[i].start <= ts and self.data[i].end >= ts:
480 found = i
481 end = i
482 continue
483
484 if self.data[i].end < ts:
485 start = i
486
487 elif self.data[i].start > ts:
488 end = i
489
490 return found
491
492 def interval(self):
493 if len(self.data) == 0:
494 return (0, 0)
495
496 return (self.data[0].start, self.data[-1].end)
497
498
499class SchedEventProxy:
500 def __init__(self):
501 self.current_tsk = defaultdict(lambda : -1)
502 self.timeslices = TimeSliceList()
503
504 def sched_switch(self, headers, prev_comm, prev_pid, prev_prio, prev_state,
505 next_comm, next_pid, next_prio):
506 """ Ensure the task we sched out this cpu is really the one
507 we logged. Otherwise we may have missed traces """
508
509 on_cpu_task = self.current_tsk[headers.cpu]
510
511 if on_cpu_task != -1 and on_cpu_task != prev_pid:
512 print "Sched switch event rejected ts: %s cpu: %d prev: %s(%d) next: %s(%d)" % \
513 (headers.ts_format(), headers.cpu, prev_comm, prev_pid, next_comm, next_pid)
514
515 threads[prev_pid] = prev_comm
516 threads[next_pid] = next_comm
517 self.current_tsk[headers.cpu] = next_pid
518
519 ts = self.timeslices.get_time_slice(headers.ts())
520 ts.sched_switch(self.timeslices, prev_pid, prev_state, next_pid, headers.cpu)
521
522 def migrate(self, headers, pid, prio, orig_cpu, dest_cpu):
523 ts = self.timeslices.get_time_slice(headers.ts())
524 ts.migrate(self.timeslices, pid, orig_cpu, dest_cpu)
525
526 def wake_up(self, headers, comm, pid, success, target_cpu, fork):
527 if success == 0:
528 return
529 ts = self.timeslices.get_time_slice(headers.ts())
530 ts.wake_up(self.timeslices, pid, target_cpu, fork)
531
532
533def trace_begin():
534 global parser
535 parser = SchedEventProxy()
536
537def trace_end():
538 app = wx.App(False)
539 timeslices = parser.timeslices
540 frame = RootFrame(timeslices)
541 app.MainLoop()
542
543def sched__sched_stat_runtime(event_name, context, common_cpu,
544 common_secs, common_nsecs, common_pid, common_comm,
545 comm, pid, runtime, vruntime):
546 pass
547
548def sched__sched_stat_iowait(event_name, context, common_cpu,
549 common_secs, common_nsecs, common_pid, common_comm,
550 comm, pid, delay):
551 pass
552
553def sched__sched_stat_sleep(event_name, context, common_cpu,
554 common_secs, common_nsecs, common_pid, common_comm,
555 comm, pid, delay):
556 pass
557
558def sched__sched_stat_wait(event_name, context, common_cpu,
559 common_secs, common_nsecs, common_pid, common_comm,
560 comm, pid, delay):
561 pass
562
563def sched__sched_process_fork(event_name, context, common_cpu,
564 common_secs, common_nsecs, common_pid, common_comm,
565 parent_comm, parent_pid, child_comm, child_pid):
566 pass
567
568def sched__sched_process_wait(event_name, context, common_cpu,
569 common_secs, common_nsecs, common_pid, common_comm,
570 comm, pid, prio):
571 pass
572
573def sched__sched_process_exit(event_name, context, common_cpu,
574 common_secs, common_nsecs, common_pid, common_comm,
575 comm, pid, prio):
576 pass
577
578def sched__sched_process_free(event_name, context, common_cpu,
579 common_secs, common_nsecs, common_pid, common_comm,
580 comm, pid, prio):
581 pass
582
583def sched__sched_migrate_task(event_name, context, common_cpu,
584 common_secs, common_nsecs, common_pid, common_comm,
585 comm, pid, prio, orig_cpu,
586 dest_cpu):
587 headers = EventHeaders(common_cpu, common_secs, common_nsecs,
588 common_pid, common_comm)
589 parser.migrate(headers, pid, prio, orig_cpu, dest_cpu)
590
591def sched__sched_switch(event_name, context, common_cpu,
592 common_secs, common_nsecs, common_pid, common_comm,
593 prev_comm, prev_pid, prev_prio, prev_state,
594 next_comm, next_pid, next_prio):
595
596 headers = EventHeaders(common_cpu, common_secs, common_nsecs,
597 common_pid, common_comm)
598 parser.sched_switch(headers, prev_comm, prev_pid, prev_prio, prev_state,
599 next_comm, next_pid, next_prio)
600
601def sched__sched_wakeup_new(event_name, context, common_cpu,
602 common_secs, common_nsecs, common_pid, common_comm,
603 comm, pid, prio, success,
604 target_cpu):
605 headers = EventHeaders(common_cpu, common_secs, common_nsecs,
606 common_pid, common_comm)
607 parser.wake_up(headers, comm, pid, success, target_cpu, 1)
608
609def sched__sched_wakeup(event_name, context, common_cpu,
610 common_secs, common_nsecs, common_pid, common_comm,
611 comm, pid, prio, success,
612 target_cpu):
613 headers = EventHeaders(common_cpu, common_secs, common_nsecs,
614 common_pid, common_comm)
615 parser.wake_up(headers, comm, pid, success, target_cpu, 0)
616
617def sched__sched_wait_task(event_name, context, common_cpu,
618 common_secs, common_nsecs, common_pid, common_comm,
619 comm, pid, prio):
620 pass
621
622def sched__sched_kthread_stop_ret(event_name, context, common_cpu,
623 common_secs, common_nsecs, common_pid, common_comm,
624 ret):
625 pass
626
627def sched__sched_kthread_stop(event_name, context, common_cpu,
628 common_secs, common_nsecs, common_pid, common_comm,
629 comm, pid):
630 pass
631
632def trace_unhandled(event_name, context, common_cpu, common_secs, common_nsecs,
633 common_pid, common_comm):
634 pass