CMS 3D CMS Logo

Classes | Functions
edmStreamStallGrapher Namespace Reference

Classes

class  Point
 
class  Stack
 
class  StreamInfoElement
 

Functions

def adjacentDiff (pairLists)
 
def chooseParser (inputFile)
 
def consolidateContiguousBlocks (numStreams, streamInfo)
 
def createAsciiImage (processingSteps, numStreams, maxNameSize)
 
def createPDFImage (pdfFile, shownStacks, processingSteps, numStreams, stalledModuleInfo)
 
def findStalledModules (processingSteps, numStreams)
 
def getTime (line)
 
def mergeContiguousBlocks (blocks)
 
def parseStallMonitorOutput (f)
 
def parseTracerOutput (f)
 
def printHelp ()
 
def printStalledModulesInOrder (stalledModules)
 
def readLogFile (inputFile)
 
def reduceSortedPoints (ps)
 

Function Documentation

def edmStreamStallGrapher.adjacentDiff (   pairLists)

Definition at line 341 of file edmStreamStallGrapher.py.

Referenced by createPDFImage().

341 def adjacentDiff(*pairLists):
342  points = []
343  for pairList in pairLists:
344  points += [Point(x[0], 1) for x in pairList if x[1] != 0]
345  points += [Point(sum(x),-1) for x in pairList if x[1] != 0]
346  points.sort(key=attrgetter('x'))
347  return points
348 
def edmStreamStallGrapher.chooseParser (   inputFile)

Definition at line 187 of file edmStreamStallGrapher.py.

References cmsRelvalreport.exit.

Referenced by readLogFile().

187 def chooseParser(inputFile):
188  firstLine = inputFile.readline().rstrip()
189  inputFile.seek(0) # Rewind back to beginning
190 
191  if firstLine.find("# Step") != -1:
192  print "> ... Parsing StallMonitor output."
193  return parseStallMonitorOutput
194  elif firstLine.find("++") != -1:
195  global kTracerInput
196  kTracerInput = True
197  print "> ... Parsing Tracer output."
198  return parseTracerOutput
199  else:
200  inputFile.close()
201  print "Unknown input format."
202  exit(1)
203 
204 #----------------------------------------------
def edmStreamStallGrapher.consolidateContiguousBlocks (   numStreams,
  streamInfo 
)

Definition at line 379 of file edmStreamStallGrapher.py.

References mps_alisetup.append, and timeUnitHelper.unpack().

Referenced by createPDFImage().

379 def consolidateContiguousBlocks(numStreams, streamInfo):
380  oldStreamInfo = streamInfo
381  streamInfo = [[] for x in xrange(numStreams)]
382 
383  for s in xrange(numStreams):
384  lastStartTime,lastTimeLength,lastColor = oldStreamInfo[s][0].unpack()
385  for info in oldStreamInfo[s][1:]:
386  start,length,color = info.unpack()
387  if color == lastColor and lastStartTime+lastTimeLength == start:
388  lastTimeLength += length
389  else:
390  streamInfo[s].append(StreamInfoElement(lastStartTime,lastTimeLength,lastColor))
391  lastStartTime = start
392  lastTimeLength = length
393  lastColor = color
394  streamInfo[s].append(StreamInfoElement(lastStartTime,lastTimeLength,lastColor))
395 
396  return streamInfo
397 
398 #----------------------------------------------
399 # Consolidating contiguous blocks with the same color drastically
400 # reduces the size of the pdf file. Same functionality as the
401 # previous function, but with slightly different implementation.
def consolidateContiguousBlocks(numStreams, streamInfo)
def edmStreamStallGrapher.createAsciiImage (   processingSteps,
  numStreams,
  maxNameSize 
)

Definition at line 246 of file edmStreamStallGrapher.py.

References harvestTrackValidationPlots.str.

Referenced by createPDFImage().

246 def createAsciiImage(processingSteps, numStreams, maxNameSize):
247  streamTime = [0]*numStreams
248  streamState = [0]*numStreams
249  modulesActiveOnStreams = [{} for x in xrange(numStreams)]
250  for n,trans,s,time in processingSteps:
251  modulesActiveOnStream = modulesActiveOnStreams[s]
252  waitTime = None
253  if trans == kPrefetchEnd:
254  modulesActiveOnStream[n] = time
255  continue
256  if trans == kStarted:
257  if n != kSourceFindEvent:
258  streamState[s] +=1
259  if n in modulesActiveOnStream:
260  waitTime = time - modulesActiveOnStream[n]
261  if n == kSourceDelayedRead:
262  if streamState[s] == 0:
263  waitTime = time-streamTime[s]
264  if trans == kFinished:
265  if n != kSourceDelayedRead and n!=kSourceFindEvent:
266  modulesActiveOnStream.pop(n, None)
267  if n != kSourceFindEvent:
268  streamState[s] -=1
269  streamTime[s] = time
270  states = "%-*s: " % (maxNameSize,n)
271  if trans == kStarted:
272  states +="+ "
273  if trans == kFinished:
274  states +="- "
275  for index, state in enumerate(streamState):
276  if n==kSourceFindEvent and index == s:
277  states +="* "
278  else:
279  states +=str(state)+" "
280  if waitTime is not None:
281  states += " %.2f"% (waitTime/1000.)
282  if waitTime > kStallThreshold:
283  states += " STALLED "+str(time/1000.)+" "+str(s)
284 
285  print states
286  return stalledModules
287 
288 #----------------------------------------------
def createAsciiImage(processingSteps, numStreams, maxNameSize)
def edmStreamStallGrapher.createPDFImage (   pdfFile,
  shownStacks,
  processingSteps,
  numStreams,
  stalledModuleInfo 
)

Definition at line 420 of file edmStreamStallGrapher.py.

References PVValHelper.add(), adjacentDiff(), mps_alisetup.append, consolidateContiguousBlocks(), createAsciiImage(), cmsRelvalreport.exit, findStalledModules(), SiStripPI.max, mergeContiguousBlocks(), min(), printHelp(), printStalledModulesInOrder(), readLogFile(), reduceSortedPoints(), MatrixUtil.remove(), and ComparisonHelper.zip().

420 def createPDFImage(pdfFile, shownStacks, processingSteps, numStreams, stalledModuleInfo):
421 
422  stalledModuleNames = set([x for x in stalledModuleInfo.iterkeys()])
423  streamInfo = [[] for x in xrange(numStreams)]
424  modulesActiveOnStreams = [{} for x in xrange(numStreams)]
425  streamLastEventEndTimes = [None]*numStreams
426  streamRunningTimes = [[] for x in xrange(numStreams)]
427  maxNumberOfConcurrentModulesOnAStream = 1
428  streamInvertedMessageFromModule = [set() for x in xrange(numStreams)]
429 
430  for n,trans,s,time in processingSteps:
431  startTime = None
432  if streamLastEventEndTimes[s] is None:
433  streamLastEventEndTimes[s]=time
434  if trans == kStarted:
435  if n == kSourceFindEvent:
436  # We assume the time from the end of the last event
437  # for a stream until the start of a new event for that
438  # stream is taken up by the source.
439  startTime = streamLastEventEndTimes[s]
440  moduleNames = set(n)
441  else:
442  activeModules = modulesActiveOnStreams[s]
443  moduleNames = set(activeModules.iterkeys())
444  if n in streamInvertedMessageFromModule[s] and kTracerInput:
445  # This is the rare case where a finished message
446  # is issued before the corresponding started.
447  streamInvertedMessageFromModule[s].remove(n)
448  continue
449  activeModules[n] = time
450  nModulesRunning = len(activeModules)
451  streamRunningTimes[s].append(Point(time,1))
452  maxNumberOfConcurrentModulesOnAStream = max(maxNumberOfConcurrentModulesOnAStream, nModulesRunning)
453  if nModulesRunning > 1:
454  # Need to create a new time span to avoid overlaps in graph.
455  startTime = min(activeModules.itervalues())
456  for k in activeModules.iterkeys():
457  activeModules[k]=time
458 
459  if trans == kFinished:
460  if n == kSourceFindEvent:
461  streamLastEventEndTimes[s]=time
462  else:
463  activeModules = modulesActiveOnStreams[s]
464  if n not in activeModules and kTracerInput:
465  # This is the rare case where a finished message
466  # is issued before the corresponding started.
467  streamInvertedMessageFromModule[s].add(n)
468  continue
469  streamRunningTimes[s].append(Point(time,-1))
470  startTime = activeModules[n]
471  moduleNames = set(activeModules.iterkeys())
472  del activeModules[n]
473  nModulesRunning = len(activeModules)
474  if nModulesRunning > 0:
475  # Reset start time for remaining modules to this time
476  # to avoid overlapping time ranges when making the plot.
477  for k in activeModules.iterkeys():
478  activeModules[k] = time
479  if startTime is not None:
480  c="green"
481  if (kSourceDelayedRead in moduleNames) or (kSourceFindEvent in moduleNames):
482  c = "orange"
483  else:
484  for n in moduleNames:
485  if n in stalledModuleNames:
486  c="red"
487  break
488  streamInfo[s].append(StreamInfoElement(startTime, time-startTime, c))
489 
490  streamInfo = consolidateContiguousBlocks(numStreams, streamInfo)
491 
492  nr = 1
493  if shownStacks:
494  nr += 1
495  fig, ax = plt.subplots(nrows=nr, squeeze=True)
496  axStack = None
497  if shownStacks:
498  [xH,yH] = fig.get_size_inches()
499  fig.set_size_inches(xH,yH*4/3)
500  ax = plt.subplot2grid((4,1),(0,0), rowspan=3)
501  axStack = plt.subplot2grid((4,1),(3,0))
502 
503  ax.set_xlabel("Time (sec)")
504  ax.set_ylabel("Stream ID")
505  ax.set_ylim(-0.5,numStreams-0.5)
506  ax.yaxis.set_ticks(xrange(numStreams))
507 
508  height = 0.8/maxNumberOfConcurrentModulesOnAStream
509  allStackTimes={'green': [], 'red': [], 'blue': [], 'orange': []}
510  for i,perStreamInfo in enumerate(streamInfo):
511  times=[(x.begin/1000., x.delta/1000.) for x in perStreamInfo] # Scale from msec to sec.
512  colors=[x.color for x in perStreamInfo]
513  ax.broken_barh(times,(i-0.4,height),facecolors=colors,edgecolors=colors,linewidth=0)
514  for info in perStreamInfo:
515  allStackTimes[info.color].append((info.begin, info.delta))
516 
517  # Now superimpose the number of concurrently running modules on to the graph.
518  if maxNumberOfConcurrentModulesOnAStream > 1:
519 
520  for i,perStreamRunningTimes in enumerate(streamRunningTimes):
521  perStreamTimes = sorted(perStreamRunningTimes, key=attrgetter('x'))
522  perStreamTimes = reduceSortedPoints(perStreamTimes)
523  streamHeight = 0
524  preparedTimes = []
525  for t1,t2 in zip(perStreamTimes, perStreamTimes[1:]):
526  streamHeight += t1.y
527  if streamHeight < 2:
528  continue
529  preparedTimes.append((t1.x,t2.x-t1.x, streamHeight))
530  preparedTimes.sort(key=itemgetter(2))
531  preparedTimes = mergeContiguousBlocks(preparedTimes)
532  for nthreads, ts in groupby(preparedTimes, itemgetter(2)):
533  theTS = [(t[0],t[1]) for t in ts]
534  theTimes = [(t[0]/1000.,t[1]/1000.) for t in theTS]
535  yspan = (i-0.4+height,height*(nthreads-1))
536  ax.broken_barh(theTimes, yspan, facecolors='blue', edgecolors='blue', linewidth=0)
537  allStackTimes['blue'].extend(theTS*(nthreads-1))
538 
539  if shownStacks:
540  print "> ... Generating stack"
541  stack = Stack()
542  for color in ['green','blue','red','orange']:
543  tmp = allStackTimes[color]
544  tmp = reduceSortedPoints(adjacentDiff(tmp))
545  stack.update(color, tmp)
546 
547  for stk in reversed(stack.data):
548  color = stk[0]
549 
550  # Now arrange list in a manner that it can be grouped by the height of the block
551  height = 0
552  xs = []
553  for p1,p2 in zip(stk[1], stk[1][1:]):
554  height += p1.y
555  xs.append((p1.x, p2.x-p1.x, height))
556  xs.sort(key = itemgetter(2))
557  xs = mergeContiguousBlocks(xs)
558 
559  for height, xpairs in groupby(xs, itemgetter(2)):
560  finalxs = [(e[0]/1000.,e[1]/1000.) for e in xpairs]
561  axStack.broken_barh(finalxs, (0, height), facecolors=color, edgecolors=color, linewidth=0)
562 
563  axStack.set_xlabel("Time (sec)");
564  axStack.set_ylabel("# threads");
565  axStack.set_xlim(ax.get_xlim())
566  axStack.tick_params(top='off')
567 
568  fig.text(0.1, 0.95, "modules running", color = "green", horizontalalignment = 'left')
569  fig.text(0.5, 0.95, "stalled module running", color = "red", horizontalalignment = 'center')
570  fig.text(0.9, 0.95, "read from input", color = "orange", horizontalalignment = 'right')
571  fig.text(0.5, 0.92, "multiple modules running", color = "blue", horizontalalignment = 'center')
572  print "> ... Saving to file: '{}'".format(pdfFile)
573  plt.savefig(pdfFile)
574 
575 #=======================================
def createPDFImage(pdfFile, shownStacks, processingSteps, numStreams, stalledModuleInfo)
def consolidateContiguousBlocks(numStreams, streamInfo)
OutputIterator zip(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result, Compare comp)
T min(T a, T b)
Definition: MathUtil.h:58
void add(std::map< std::string, TH1 * > &h, TH1 *hist)
def remove(d, key, TELL=False)
Definition: MatrixUtil.py:209
def edmStreamStallGrapher.findStalledModules (   processingSteps,
  numStreams 
)

Definition at line 219 of file edmStreamStallGrapher.py.

Referenced by createPDFImage().

219 def findStalledModules(processingSteps, numStreams):
220  streamTime = [0]*numStreams
221  stalledModules = {}
222  modulesActiveOnStream = [{} for x in xrange(numStreams)]
223  for n,trans,s,time in processingSteps:
224  waitTime = None
225  modulesOnStream = modulesActiveOnStream[s]
226  if trans == kPrefetchEnd:
227  modulesOnStream[n] = time
228  if trans == kStarted:
229  if n in modulesOnStream:
230  waitTime = time - modulesOnStream[n]
231  if n == kSourceDelayedRead:
232  if 0 == len(modulesOnStream):
233  waitTime = time - streamTime[s]
234  if trans == kFinished:
235  if n != kSourceDelayedRead and n!=kSourceFindEvent:
236  modulesOnStream.pop(n, None)
237  streamTime[s] = time
238  if waitTime is not None:
239  if waitTime > kStallThreshold:
240  t = stalledModules.setdefault(n,[])
241  t.append(waitTime)
242  return stalledModules
243 
244 
245 #----------------------------------------------
def findStalledModules(processingSteps, numStreams)
def edmStreamStallGrapher.getTime (   line)

Definition at line 115 of file edmStreamStallGrapher.py.

References objects.autophobj.float, and createfilelist.int.

Referenced by EcalPerEvtLaserAnalyzer.endJob(), EcalLaserAnalyzer2.endJob(), EcalLaserAnalyzer.endJob(), parseTracerOutput(), and pos::PixelFEDTestDAC.writeXMLHeader().

115 def getTime(line):
116  time = line.split(" ")[1]
117  time = time.split(":")
118  time = int(time[0])*60*60+int(time[1])*60+float(time[2])
119  time = int(1000*time) # convert to milliseconds
120  return time
121 
122 #----------------------------------------------
def edmStreamStallGrapher.mergeContiguousBlocks (   blocks)

Definition at line 402 of file edmStreamStallGrapher.py.

Referenced by createPDFImage().

403  oldBlocks = blocks
404 
405  blocks = []
406  lastStartTime,lastTimeLength,lastHeight = oldBlocks[0]
407  for start,length,height in oldBlocks[1:]:
408  if height == lastHeight and lastStartTime+lastTimeLength == start:
409  lastTimeLength += length
410  else:
411  blocks.append((lastStartTime,lastTimeLength,lastHeight))
412  lastStartTime = start
413  lastTimeLength = length
414  lastHeight = height
415  blocks.append((lastStartTime,lastTimeLength,lastHeight))
416 
417  return blocks
418 
419 #----------------------------------------------
def edmStreamStallGrapher.parseStallMonitorOutput (   f)

Definition at line 52 of file edmStreamStallGrapher.py.

References createfilelist.int, SiStripPI.max, and split.

53  processingSteps = []
54  numStreams = 0
55  maxNameSize = 0
56  foundEventToStartFrom = False
57  moduleNames = {}
58  for rawl in f:
59  l = rawl.strip()
60  if not l or l[0] == '#':
61  if len(l) > 5 and l[0:2] == "#M":
62  (id,name)=tuple(l[2:].split())
63  moduleNames[id] = name
64  continue
65  (step,payload) = tuple(l.split(None,1))
66  payload=payload.split()
67 
68  # Payload format is:
69  # <stream id> <..other fields..> <time since begin job>
70  stream = int(payload[0])
71  time = int(payload[-1])
72  trans = None
73 
74  numStreams = max(numStreams, stream+1)
75 
76  # 'S' = begin of event creation in source
77  # 's' = end of event creation in source
78  if step == 'S' or step == 's':
79  name = kSourceFindEvent
80  trans = kStarted
81  # The start of an event is the end of the framework part
82  if step == 's':
83  trans = kFinished
84  else:
85  # moduleID is the second payload argument for all steps below
86  moduleID = payload[1]
87 
88  # 'p' = end of module prefetching
89  # 'M' = begin of module processing
90  # 'm' = end of module processing
91  if step == 'p' or step == 'M' or step == 'm':
92  trans = kStarted
93  if step == 'p':
94  trans = kPrefetchEnd
95  elif step == 'm':
96  trans = kFinished
97  name = moduleNames[moduleID]
98 
99  # Delayed read from source
100  # 'R' = begin of delayed read from source
101  # 'r' = end of delayed read from source
102  if step == 'R' or step == 'r':
103  trans = kStarted
104  if step == 'r':
105  trans = kFinished
106  name = kSourceDelayedRead
107 
108  maxNameSize = max(maxNameSize, len(name))
109  processingSteps.append((name,trans,stream,time))
110 
111  f.close()
112  return (processingSteps,numStreams,maxNameSize)
113 
114 #----------------------------------------------
double split
Definition: MVATrainer.cc:139
def edmStreamStallGrapher.parseTracerOutput (   f)

Definition at line 123 of file edmStreamStallGrapher.py.

References getTime(), createfilelist.int, and SiStripPI.max.

124  processingSteps = []
125  numStreams = 0
126  maxNameSize = 0
127  startTime = 0
128  for l in f:
129  if l.find("processing event :") != -1:
130  time = getTime(l)
131  if startTime == 0:
132  startTime = time
133  time = time - startTime
134  streamIndex = l.find("stream = ")
135  stream = int(l[streamIndex+9:l.find(" ",streamIndex+10)])
136  name = kSourceFindEvent
137  trans = kFinished
138  #the start of an event is the end of the framework part
139  if l.find("starting:") != -1:
140  trans = kStarted
141  processingSteps.append((name,trans,stream,time))
142  numStreams = max(numStreams, stream+1)
143  if l.find("processing event for module") != -1:
144  time = getTime(l)
145  if startTime == 0:
146  startTime = time
147  time = time - startTime
148  trans = kStarted
149  stream = 0
150  delayed = False
151  if l.find("finished:") != -1:
152  if l.find("prefetching") != -1:
153  trans = kPrefetchEnd
154  else:
155  trans = kFinished
156  else:
157  if l.find("prefetching") != -1:
158  #skip this since we don't care about prefetch starts
159  continue
160  streamIndex = l.find("stream = ")
161  stream = int( l[streamIndex+9:l.find(" ",streamIndex+10)])
162  name = l.split("'")[1]
163  maxNameSize = max(maxNameSize, len(name))
164  processingSteps.append((name,trans,stream,time))
165  numStreams = max(numStreams, stream+1)
166  if l.find("event delayed read from source") != -1:
167  time = getTime(l)
168  if startTime == 0:
169  startTime = time
170  time = time - startTime
171  trans = kStarted
172  stream = 0
173  delayed = False
174  if l.find("finished:") != -1:
175  trans = kFinished
176  streamIndex = l.find("stream = ")
177  stream = int(l[streamIndex+9:l.find(" ",streamIndex+10)])
178  name = kSourceDelayedRead
179  maxNameSize = max(maxNameSize, len(name))
180  processingSteps.append((name,trans,stream,time))
181  numStreams = max(numStreams, stream+1)
182  f.close()
183  return (processingSteps,numStreams,maxNameSize)
184 
185 
186 #----------------------------------------------
def edmStreamStallGrapher.printHelp ( )

Definition at line 7 of file edmStreamStallGrapher.py.

Referenced by createPDFImage().

7 def printHelp():
8  s = '''
9 To Use: Add the Tracer Service to the cmsRun job you want to check for
10  stream stalls. Make sure to use the 'printTimstamps' option
11  cms.Service("Tracer", printTimestamps = cms.untracked.bool(True))
12  After running the job, execute this script and pass the name of the
13  log file to the script as the only command line argument.
14 
15 To Read: The script will then print an 'ASCII art' stall graph which
16  consists of the name of the module which either started or stopped
17  running on a stream, and the number of modules running on each
18  stream at that the moment in time. If the module just started, you
19  will also see the amount of time the module spent between finishing
20  its prefetching and starting. The state of a module is represented
21  by a symbol:
22 
23  plus ("+") the stream has just finished waiting and is starting a module
24  minus ("-") the stream just finished running a module
25 
26  If a module had to wait more than 0.1 seconds, the end of the line
27  will have "STALLED". Once the first 4 events have finished
28  processing, the program prints "FINISH INIT". This is useful if one
29  wants to ignore stalled caused by startup actions, e.g. reading
30  conditions.
31 
32  Once the graph is completed, the program outputs the list of modules
33  which had the greatest total stall times. The list is sorted by
34  total stall time and written in descending order. In addition, the
35  list of all stall times for the module is given.'''
36  return s
37 
38 
39 kStallThreshold=100 #in milliseconds
40 kTracerInput=False
41 
42 #Stream states
43 kStarted=0
44 kFinished=1
45 kPrefetchEnd=2
46 
47 #Special names
def edmStreamStallGrapher.printStalledModulesInOrder (   stalledModules)

Definition at line 289 of file edmStreamStallGrapher.py.

References join(), and SiStripPI.max.

Referenced by createPDFImage().

289 def printStalledModulesInOrder(stalledModules):
290  priorities = []
291  maxNameSize = 0
292  for name,t in stalledModules.iteritems():
293  maxNameSize = max(maxNameSize, len(name))
294  t.sort(reverse=True)
295  priorities.append((name,sum(t),t))
296 
297  def sumSort(i,j):
298  return cmp(i[1],j[1])
299  priorities.sort(cmp=sumSort, reverse=True)
300 
301  nameColumn = "Stalled Module"
302  maxNameSize = max(maxNameSize, len(nameColumn))
303 
304  stallColumn = "Tot Stall Time"
305  stallColumnLength = len(stallColumn)
306 
307  print "%-*s" % (maxNameSize, nameColumn), "%-*s"%(stallColumnLength,stallColumn), " Stall Times"
308  for n,s,t in priorities:
309  paddedName = "%-*s:" % (maxNameSize,n)
310  print paddedName, "%-*.2f"%(stallColumnLength,s/1000.), ", ".join([ "%.2f"%(x/1000.) for x in t])
311 
312 #--------------------------------------------------------
def printStalledModulesInOrder(stalledModules)
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def edmStreamStallGrapher.readLogFile (   inputFile)

Definition at line 205 of file edmStreamStallGrapher.py.

References chooseParser(), and tools.parseInput().

Referenced by createPDFImage().

205 def readLogFile(inputFile):
206  parseInput = chooseParser(inputFile)
207  return parseInput(inputFile)
208 
209 #----------------------------------------------
210 # Patterns:
211 #
212 # source: The source just records how long it was spent doing work,
213 # not how long it was stalled. We can get a lower bound on the stall
214 # time by measuring the time the stream was doing no work up till
215 # the source was run.
216 # modules: The time between prefetch finished and 'start processing' is
217 # the time it took to acquire any resources
218 #
def parseInput(inputFields, requiredFields=())
Definition: tools.py:122
def edmStreamStallGrapher.reduceSortedPoints (   ps)

Definition at line 325 of file edmStreamStallGrapher.py.

Referenced by createPDFImage(), and edmStreamStallGrapher.Stack.update().

326  if len(ps) < 2:
327  return ps
328  reducedPoints = []
329  tmp = ps[0]
330  for p in ps[1:]:
331  if tmp.x == p.x:
332  tmp.y += p.y
333  else:
334  reducedPoints.append(tmp)
335  tmp = p
336  reducedPoints.append(tmp)
337  reducedPoints = [p for p in reducedPoints if p.y != 0]
338  return reducedPoints
339 
340 # -------------------------------------------