|  | @@ -1,6 +1,9 @@
 | 
	
		
			
				|  |  |  import time
 | 
	
		
			
				|  |  |  import heapq
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +from collections import deque
 | 
	
		
			
				|  |  | +from threading import RLock
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  from carrot.utils import partition
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  from celery import states
 | 
	
	
		
			
				|  | @@ -50,7 +53,7 @@ class Worker(Element):
 | 
	
		
			
				|  |  |  class Task(Element):
 | 
	
		
			
				|  |  |      """Task State."""
 | 
	
		
			
				|  |  |      _info_fields = ("args", "kwargs", "retries",
 | 
	
		
			
				|  |  | -                    "result", "eta", "runtime",
 | 
	
		
			
				|  |  | +                    "result", "eta", "runtime", "expires",
 | 
	
		
			
				|  |  |                      "exception")
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      _defaults = dict(uuid=None,
 | 
	
	
		
			
				|  | @@ -65,9 +68,14 @@ class Task(Element):
 | 
	
		
			
				|  |  |                       args=None,
 | 
	
		
			
				|  |  |                       kwargs=None,
 | 
	
		
			
				|  |  |                       eta=None,
 | 
	
		
			
				|  |  | +                     expires=None,
 | 
	
		
			
				|  |  |                       retries=None,
 | 
	
		
			
				|  |  |                       worker=None,
 | 
	
		
			
				|  |  | -                     timestamp=None)
 | 
	
		
			
				|  |  | +                     result=None,
 | 
	
		
			
				|  |  | +                     exception=None,
 | 
	
		
			
				|  |  | +                     timestamp=None,
 | 
	
		
			
				|  |  | +                     runtime=None,
 | 
	
		
			
				|  |  | +                     traceback=None)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def __init__(self, **fields):
 | 
	
		
			
				|  |  |          super(Task, self).__init__(**dict(self._defaults, **fields))
 | 
	
	
		
			
				|  | @@ -121,9 +129,58 @@ class Task(Element):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  class State(object):
 | 
	
		
			
				|  |  | -    """Represents a snapshot of a clusters state."""
 | 
	
		
			
				|  |  | +    """Records clusters state."""
 | 
	
		
			
				|  |  |      event_count = 0
 | 
	
		
			
				|  |  |      task_count = 0
 | 
	
		
			
				|  |  | +    _buffering = False
 | 
	
		
			
				|  |  | +    buffer = deque()
 | 
	
		
			
				|  |  | +    frozen = False
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def freeze(self, buffer=True):
 | 
	
		
			
				|  |  | +        """Stop recording the event stream.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        :keyword buffer: If true, any events received while frozen
 | 
	
		
			
				|  |  | +           will be buffered, you can use ``thaw(replay=True)`` to apply
 | 
	
		
			
				|  |  | +           this buffer. :meth:`thaw` will clear the buffer and resume
 | 
	
		
			
				|  |  | +           recording the stream.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        self._buffering = buffer
 | 
	
		
			
				|  |  | +        self.frozen = True
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _replay(self):
 | 
	
		
			
				|  |  | +        while self.buffer:
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                event = self.buffer.popleft()
 | 
	
		
			
				|  |  | +            except IndexError:
 | 
	
		
			
				|  |  | +                pass
 | 
	
		
			
				|  |  | +            self._dispatch_event(event)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def thaw(self, replay=True):
 | 
	
		
			
				|  |  | +        """Resume recording of the event stream.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        :keyword replay: Will replay buffered events received while
 | 
	
		
			
				|  |  | +          the stream was frozen.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        This will always clear the buffer, deleting any events collected
 | 
	
		
			
				|  |  | +        while the stream was frozen.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        self._buffering = False
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            if replay:
 | 
	
		
			
				|  |  | +                self._replay()
 | 
	
		
			
				|  |  | +            else:
 | 
	
		
			
				|  |  | +                self.buffer.clear()
 | 
	
		
			
				|  |  | +        finally:
 | 
	
		
			
				|  |  | +            self.frozen = False
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def freeze_while(self, fun, *args, **kwargs):
 | 
	
		
			
				|  |  | +        self.freeze()
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            return fun(*args, **kwargs)
 | 
	
		
			
				|  |  | +        finally:
 | 
	
		
			
				|  |  | +            self.thaw(replay=True)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def __init__(self, callback=None,
 | 
	
		
			
				|  |  |              max_workers_in_memory=5000, max_tasks_in_memory=10000):
 | 
	
	
		
			
				|  | @@ -132,6 +189,16 @@ class State(object):
 | 
	
		
			
				|  |  |          self.event_callback = callback
 | 
	
		
			
				|  |  |          self.group_handlers = {"worker": self.worker_event,
 | 
	
		
			
				|  |  |                                 "task": self.task_event}
 | 
	
		
			
				|  |  | +        self._resource = RLock()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def clear(self):
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            self.workers.clear()
 | 
	
		
			
				|  |  | +            self.tasks.clear()
 | 
	
		
			
				|  |  | +            self.event_count = 0
 | 
	
		
			
				|  |  | +            self.task_count = 0
 | 
	
		
			
				|  |  | +        finally:
 | 
	
		
			
				|  |  | +            pass
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def get_or_create_worker(self, hostname, **kwargs):
 | 
	
		
			
				|  |  |          """Get or create worker by hostname."""
 | 
	
	
		
			
				|  | @@ -173,8 +240,7 @@ class State(object):
 | 
	
		
			
				|  |  |              handler(**fields)
 | 
	
		
			
				|  |  |          task.worker = worker
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    def event(self, event):
 | 
	
		
			
				|  |  | -        """Process event."""
 | 
	
		
			
				|  |  | +    def _dispatch_event(self, event):
 | 
	
		
			
				|  |  |          self.event_count += 1
 | 
	
		
			
				|  |  |          event = kwdict(event)
 | 
	
		
			
				|  |  |          group, _, type = partition(event.pop("type"), "-")
 | 
	
	
		
			
				|  | @@ -182,6 +248,16 @@ class State(object):
 | 
	
		
			
				|  |  |          if self.event_callback:
 | 
	
		
			
				|  |  |              self.event_callback(self, event)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    def event(self, event):
 | 
	
		
			
				|  |  | +        """Process event."""
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            if not self.frozen:
 | 
	
		
			
				|  |  | +                self._dispatch_event(event)
 | 
	
		
			
				|  |  | +            elif self._buffering:
 | 
	
		
			
				|  |  | +                self.buffer.append(event)
 | 
	
		
			
				|  |  | +        finally:
 | 
	
		
			
				|  |  | +            pass
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      def tasks_by_timestamp(self, limit=None):
 | 
	
		
			
				|  |  |          """Get tasks by timestamp.
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -223,5 +299,9 @@ class State(object):
 | 
	
		
			
				|  |  |          """Returns a list of (seemingly) alive workers."""
 | 
	
		
			
				|  |  |          return [w for w in self.workers.values() if w.alive]
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    def __repr__(self):
 | 
	
		
			
				|  |  | +        return "<ClusterState: events=%s tasks=%s>" % (self.event_count,
 | 
	
		
			
				|  |  | +                                                       self.task_count)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  state = State()
 |