Patchwork [bitbake-devel] knotty, xmlrpc: add observer-only mode

login
register
mail settings
Submitter Alexandru DAMIAN
Date June 7, 2013, 3:42 p.m.
Message ID <1370619726-22780-1-git-send-email-alexandru.damian@intel.com>
Download mbox | patch
Permalink /patch/51335/
State New
Headers show

Comments

Alexandru DAMIAN - June 7, 2013, 3:42 p.m.
From: Alexandru DAMIAN <alexandru.damian@intel.com>

I add an observer only mode for the knotty UI and
the XMLRPC server that will allow the UI to register
a callback with a server in order to receive events.

The observer-UI will not send any commands to the
server apart from registering as an event handler.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bin/bitbake             |  8 +++++++-
 lib/bb/server/xmlrpc.py | 18 ++++++++++++------
 lib/bb/ui/knotty.py     | 29 +++++++++++++++++------------
 lib/bb/ui/uievent.py    |  1 +
 4 files changed, 37 insertions(+), 19 deletions(-)
Richard Purdie - June 12, 2013, 1:11 p.m.
On Fri, 2013-06-07 at 16:42 +0100, Alex DAMIAN wrote:
> From: Alexandru DAMIAN <alexandru.damian@intel.com>
> 
> I add an observer only mode for the knotty UI and
> the XMLRPC server that will allow the UI to register
> a callback with a server in order to receive events.
> 
> The observer-UI will not send any commands to the
> server apart from registering as an event handler.
> 
> Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
> ---
>  bin/bitbake             |  8 +++++++-
>  lib/bb/server/xmlrpc.py | 18 ++++++++++++------
>  lib/bb/ui/knotty.py     | 29 +++++++++++++++++------------
>  lib/bb/ui/uievent.py    |  1 +
>  4 files changed, 37 insertions(+), 19 deletions(-)
> 
> diff --git a/bin/bitbake b/bin/bitbake
> index d263cbd..ef0c5d8 100755
> --- a/bin/bitbake
> +++ b/bin/bitbake
> @@ -197,6 +197,9 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters):
>          parser.add_option("", "--remote-server", help = "Connect to the specified server",
>                     action = "store", dest = "remote_server", default = False)
>  
> +        parser.add_option("", "--observe-only", help = "Connect to a server as an observing-only client",
> +                   action = "store_true", dest = "observe_only", default = False)
> +
>          options, targets = parser.parse_args(sys.argv)
>          return options, targets[1:]
>  
> @@ -269,6 +272,9 @@ def main():
>      if configParams.remote_server and configParams.servertype != "xmlrpc":
>          sys.exit("FATAL: If '--remote-server' is defined, we must set the servertype as 'xmlrpc'.\n")
>  
> +    if configParams.observe_only and (not configParams.remote_server or configParams.bind):
> +        sys.exit("FATAL: '--observe-only' can only be used by UI clients connecting to a server.\n")
> +
>      if "BBDEBUG" in os.environ:
>          level = int(os.environ["BBDEBUG"])
>          if level > configuration.debug:
> @@ -295,7 +301,7 @@ def main():
>          server = start_server(servermodule, configParams, configuration)
>      else:
>          # we start a stub server that is actually a XMLRPClient to
> -        server = servermodule.BitBakeXMLRPCClient()
> +        server = servermodule.BitBakeXMLRPCClient(configParams.observe_only)
>          server.saveConnectionDetails(configParams.remote_server)
>  
>      logger.removeHandler(handler)
> diff --git a/lib/bb/server/xmlrpc.py b/lib/bb/server/xmlrpc.py
> index 0b51ebd..1b3502e 100644
> --- a/lib/bb/server/xmlrpc.py
> +++ b/lib/bb/server/xmlrpc.py
> @@ -214,7 +214,7 @@ class BitBakeXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
>              remote_token = self.headers["Bitbake-token"]
>          except:
>              remote_token = None
> -        if remote_token != self.connection_token:
> +        if remote_token != self.connection_token and remote_token != "observer":
>              self.report_503()
>          else:
>              SimpleXMLRPCRequestHandler.do_POST(self)
> @@ -424,13 +424,17 @@ class XMLRPCServer(SimpleXMLRPCServer, BaseImplServer):
>          self.connection_token = token
>  
>  class BitBakeXMLRPCServerConnection(BitBakeBaseServerConnection):
> -    def __init__(self, serverImpl, clientinfo=("localhost", 0)):
> +    def __init__(self, serverImpl, clientinfo=("localhost", 0), observer_only = False):
>          self.connection, self.transport = _create_server(serverImpl.host, serverImpl.port)
>          self.clientinfo = clientinfo
>          self.serverImpl = serverImpl
> +        self.observer_only = observer_only
>  
>      def connect(self):
> -        token = self.connection.addClient()
> +        if not self.observer_only:
> +            token = self.connection.addClient()
> +        else:
> +            token = "observer"
>          if token is None:
>              return None
>          self.transport.set_connection_token(token)
> @@ -440,7 +444,8 @@ class BitBakeXMLRPCServerConnection(BitBakeBaseServerConnection):
>          return self
>  
>      def removeClient(self):
> -        self.connection.removeClient()
> +        if not self.observer_only:
> +            self.connection.removeClient()
>  
>      def terminate(self):
>          # Don't wait for server indefinitely
> @@ -472,7 +477,8 @@ class BitBakeServer(BitBakeBaseServer):
>  
>  class BitBakeXMLRPCClient(BitBakeBaseServer):
>  
> -    def __init__(self):
> +    def __init__(self, observer_only = False):
> +        self.observer_only = observer_only
>          pass
>  
>      def saveConnectionDetails(self, remote):
> @@ -495,7 +501,7 @@ class BitBakeXMLRPCClient(BitBakeBaseServer):
>          except:
>              return None
>          self.serverImpl = XMLRPCProxyServer(host, port)
> -        self.connection = BitBakeXMLRPCServerConnection(self.serverImpl, (ip, 0))
> +        self.connection = BitBakeXMLRPCServerConnection(self.serverImpl, (ip, 0), self.observer_only)
>          return self.connection.connect()
>  
>      def endSession(self):
> diff --git a/lib/bb/ui/knotty.py b/lib/bb/ui/knotty.py
> index 389c3cc..465203f 100644
> --- a/lib/bb/ui/knotty.py
> +++ b/lib/bb/ui/knotty.py
> @@ -216,21 +216,28 @@ class TerminalFilter(object):
>              fd = sys.stdin.fileno()
>              self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
>  
> -def main(server, eventHandler, params, tf = TerminalFilter):
> -
> +def _log_settings_from_server(server):
>      # Get values of variables which control our output
>      includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
>      if error:
>          logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
> -        return 1
> +        raise error
>      loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
>      if error:
>          logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
> -        return 1
> +        raise error
>      consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
>      if error:
>          logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
> -        return 1
> +        raise error
> +    return includelogs, loglines, consolelogfile
> +
> +def main(server, eventHandler, params, tf = TerminalFilter):
> +
> +    if params.observe_only:
> +        includelogs, loglines, consolelogfile = None, None, None
> +    else:
> +        includelogs, loglines, consolelogfile = _log_settings_from_server(server)

This doesn't look right. Even in observe mode, I'd expect to see the
same console output. 

This raises the question, even in observe only mode, can't we run sync
commands, just not async commands or write commands?

>      if sys.stdin.isatty() and sys.stdout.isatty():
>          log_exec_tty = True
> @@ -254,7 +261,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
>          consolelog.setFormatter(conlogformat)
>          logger.addHandler(consolelog)
>  
> -    try:
> +    if not params.observe_only:
>          params.updateFromServer(server)
>          cmdline = params.parseActions()
>          if not cmdline:
> @@ -271,9 +278,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
>          elif ret != True:
>              logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
>              return 1
> -    except xmlrpclib.Fault as x:
> -        logger.error("XMLRPC Fault getting commandline:\n %s" % x)
> -        return 1
> +
>  
>      parseprogress = None
>      cacheprogress = None
> @@ -320,7 +325,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
>                  elif event.levelno == format.WARNING:
>                      warnings = warnings + 1
>                  # For "normal" logging conditions, don't show note logs from tasks
> -                # but do show them if the user has changed the default log level to 
> +                # but do show them if the user has changed the default log level to
>                  # include verbose/debug messages
>                  if event.taskpid != 0 and event.levelno <= format.NOTE:
>                      continue
> @@ -469,12 +474,12 @@ def main(server, eventHandler, params, tf = TerminalFilter):
>                  pass
>          except KeyboardInterrupt:
>              termfilter.clearFooter()
> -            if main.shutdown == 1:
> +            if not params.observe_only and main.shutdown == 1:
>                  print("\nSecond Keyboard Interrupt, stopping...\n")
>                  _, error = server.runCommand(["stateStop"])
>                  if error:
>                      logger.error("Unable to cleanly stop: %s" % error)
> -            if main.shutdown == 0:
> +            if not params.observe_only and main.shutdown == 0:
>                  print("\nKeyboard Interrupt, closing down...\n")
>                  interrupted = True
>                  _, error = server.runCommand(["stateShutdown"])
> diff --git a/lib/bb/ui/uievent.py b/lib/bb/ui/uievent.py
> index 0b9a836..038029f 100644
> --- a/lib/bb/ui/uievent.py
> +++ b/lib/bb/ui/uievent.py
> @@ -84,6 +84,7 @@ class BBUIEventQueue:
>  
>      def startCallbackHandler(self):
>  
> +        self.server.timeout = 1
>          while not self.server.quit:
>              self.server.handle_request()
>          self.server.server_close()

Why? The commit message doesn't mention timeouts.

Cheers,

Richard
Alex Damian - June 13, 2013, 3:23 p.m.
On 06/12/2013 02:11 PM, Richard Purdie wrote:
> On Fri, 2013-06-07 at 16:42 +0100, Alex DAMIAN wrote:
>> From: Alexandru DAMIAN <alexandru.damian@intel.com>
>>
>> I add an observer only mode for the knotty UI and
>> the XMLRPC server that will allow the UI to register
>> a callback with a server in order to receive events.
>>
>> The observer-UI will not send any commands to the
>> server apart from registering as an event handler.
>>
>> Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
>> ---
>>   bin/bitbake             |  8 +++++++-
>>   lib/bb/server/xmlrpc.py | 18 ++++++++++++------
>>   lib/bb/ui/knotty.py     | 29 +++++++++++++++++------------
>>   lib/bb/ui/uievent.py    |  1 +
>>   4 files changed, 37 insertions(+), 19 deletions(-)
>>
>> diff --git a/bin/bitbake b/bin/bitbake
>> index d263cbd..ef0c5d8 100755
>> --- a/bin/bitbake
>> +++ b/bin/bitbake
>> @@ -197,6 +197,9 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters):
>>           parser.add_option("", "--remote-server", help = "Connect to the specified server",
>>                      action = "store", dest = "remote_server", default = False)
>>   
>> +        parser.add_option("", "--observe-only", help = "Connect to a server as an observing-only client",
>> +                   action = "store_true", dest = "observe_only", default = False)
>> +
>>           options, targets = parser.parse_args(sys.argv)
>>           return options, targets[1:]
>>   
>> @@ -269,6 +272,9 @@ def main():
>>       if configParams.remote_server and configParams.servertype != "xmlrpc":
>>           sys.exit("FATAL: If '--remote-server' is defined, we must set the servertype as 'xmlrpc'.\n")
>>   
>> +    if configParams.observe_only and (not configParams.remote_server or configParams.bind):
>> +        sys.exit("FATAL: '--observe-only' can only be used by UI clients connecting to a server.\n")
>> +
>>       if "BBDEBUG" in os.environ:
>>           level = int(os.environ["BBDEBUG"])
>>           if level > configuration.debug:
>> @@ -295,7 +301,7 @@ def main():
>>           server = start_server(servermodule, configParams, configuration)
>>       else:
>>           # we start a stub server that is actually a XMLRPClient to
>> -        server = servermodule.BitBakeXMLRPCClient()
>> +        server = servermodule.BitBakeXMLRPCClient(configParams.observe_only)
>>           server.saveConnectionDetails(configParams.remote_server)
>>   
>>       logger.removeHandler(handler)
>> diff --git a/lib/bb/server/xmlrpc.py b/lib/bb/server/xmlrpc.py
>> index 0b51ebd..1b3502e 100644
>> --- a/lib/bb/server/xmlrpc.py
>> +++ b/lib/bb/server/xmlrpc.py
>> @@ -214,7 +214,7 @@ class BitBakeXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
>>               remote_token = self.headers["Bitbake-token"]
>>           except:
>>               remote_token = None
>> -        if remote_token != self.connection_token:
>> +        if remote_token != self.connection_token and remote_token != "observer":
>>               self.report_503()
>>           else:
>>               SimpleXMLRPCRequestHandler.do_POST(self)
>> @@ -424,13 +424,17 @@ class XMLRPCServer(SimpleXMLRPCServer, BaseImplServer):
>>           self.connection_token = token
>>   
>>   class BitBakeXMLRPCServerConnection(BitBakeBaseServerConnection):
>> -    def __init__(self, serverImpl, clientinfo=("localhost", 0)):
>> +    def __init__(self, serverImpl, clientinfo=("localhost", 0), observer_only = False):
>>           self.connection, self.transport = _create_server(serverImpl.host, serverImpl.port)
>>           self.clientinfo = clientinfo
>>           self.serverImpl = serverImpl
>> +        self.observer_only = observer_only
>>   
>>       def connect(self):
>> -        token = self.connection.addClient()
>> +        if not self.observer_only:
>> +            token = self.connection.addClient()
>> +        else:
>> +            token = "observer"
>>           if token is None:
>>               return None
>>           self.transport.set_connection_token(token)
>> @@ -440,7 +444,8 @@ class BitBakeXMLRPCServerConnection(BitBakeBaseServerConnection):
>>           return self
>>   
>>       def removeClient(self):
>> -        self.connection.removeClient()
>> +        if not self.observer_only:
>> +            self.connection.removeClient()
>>   
>>       def terminate(self):
>>           # Don't wait for server indefinitely
>> @@ -472,7 +477,8 @@ class BitBakeServer(BitBakeBaseServer):
>>   
>>   class BitBakeXMLRPCClient(BitBakeBaseServer):
>>   
>> -    def __init__(self):
>> +    def __init__(self, observer_only = False):
>> +        self.observer_only = observer_only
>>           pass
>>   
>>       def saveConnectionDetails(self, remote):
>> @@ -495,7 +501,7 @@ class BitBakeXMLRPCClient(BitBakeBaseServer):
>>           except:
>>               return None
>>           self.serverImpl = XMLRPCProxyServer(host, port)
>> -        self.connection = BitBakeXMLRPCServerConnection(self.serverImpl, (ip, 0))
>> +        self.connection = BitBakeXMLRPCServerConnection(self.serverImpl, (ip, 0), self.observer_only)
>>           return self.connection.connect()
>>   
>>       def endSession(self):
>> diff --git a/lib/bb/ui/knotty.py b/lib/bb/ui/knotty.py
>> index 389c3cc..465203f 100644
>> --- a/lib/bb/ui/knotty.py
>> +++ b/lib/bb/ui/knotty.py
>> @@ -216,21 +216,28 @@ class TerminalFilter(object):
>>               fd = sys.stdin.fileno()
>>               self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
>>   
>> -def main(server, eventHandler, params, tf = TerminalFilter):
>> -
>> +def _log_settings_from_server(server):
>>       # Get values of variables which control our output
>>       includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
>>       if error:
>>           logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
>> -        return 1
>> +        raise error
>>       loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
>>       if error:
>>           logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
>> -        return 1
>> +        raise error
>>       consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
>>       if error:
>>           logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
>> -        return 1
>> +        raise error
>> +    return includelogs, loglines, consolelogfile
>> +
>> +def main(server, eventHandler, params, tf = TerminalFilter):
>> +
>> +    if params.observe_only:
>> +        includelogs, loglines, consolelogfile = None, None, None
>> +    else:
>> +        includelogs, loglines, consolelogfile = _log_settings_from_server(server)
> This doesn't look right. Even in observe mode, I'd expect to see the
> same console output.
>
> This raises the question, even in observe only mode, can't we run sync
> commands, just not async commands or write commands?
>
>>       if sys.stdin.isatty() and sys.stdout.isatty():
>>           log_exec_tty = True
>> @@ -254,7 +261,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
>>           consolelog.setFormatter(conlogformat)
>>           logger.addHandler(consolelog)
>>   
>> -    try:
>> +    if not params.observe_only:
>>           params.updateFromServer(server)
>>           cmdline = params.parseActions()
>>           if not cmdline:
>> @@ -271,9 +278,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
>>           elif ret != True:
>>               logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
>>               return 1
>> -    except xmlrpclib.Fault as x:
>> -        logger.error("XMLRPC Fault getting commandline:\n %s" % x)
>> -        return 1
>> +
>>   
>>       parseprogress = None
>>       cacheprogress = None
>> @@ -320,7 +325,7 @@ def main(server, eventHandler, params, tf = TerminalFilter):
>>                   elif event.levelno == format.WARNING:
>>                       warnings = warnings + 1
>>                   # For "normal" logging conditions, don't show note logs from tasks
>> -                # but do show them if the user has changed the default log level to
>> +                # but do show them if the user has changed the default log level to
>>                   # include verbose/debug messages
>>                   if event.taskpid != 0 and event.levelno <= format.NOTE:
>>                       continue
>> @@ -469,12 +474,12 @@ def main(server, eventHandler, params, tf = TerminalFilter):
>>                   pass
>>           except KeyboardInterrupt:
>>               termfilter.clearFooter()
>> -            if main.shutdown == 1:
>> +            if not params.observe_only and main.shutdown == 1:
>>                   print("\nSecond Keyboard Interrupt, stopping...\n")
>>                   _, error = server.runCommand(["stateStop"])
>>                   if error:
>>                       logger.error("Unable to cleanly stop: %s" % error)
>> -            if main.shutdown == 0:
>> +            if not params.observe_only and main.shutdown == 0:
>>                   print("\nKeyboard Interrupt, closing down...\n")
>>                   interrupted = True
>>                   _, error = server.runCommand(["stateShutdown"])
>> diff --git a/lib/bb/ui/uievent.py b/lib/bb/ui/uievent.py
>> index 0b9a836..038029f 100644
>> --- a/lib/bb/ui/uievent.py
>> +++ b/lib/bb/ui/uievent.py
>> @@ -84,6 +84,7 @@ class BBUIEventQueue:
>>   
>>       def startCallbackHandler(self):
>>   
>> +        self.server.timeout = 1
>>           while not self.server.quit:
>>               self.server.handle_request()
>>           self.server.server_close()
> Why? The commit message doesn't mention timeouts.
It's an implementation detail. If not there, than the server will hang 
indefinitely waiting for a command, and not ever processing the 
self.server.quit variable in the while.

>
> Cheers,
>
> Richard
>
> _______________________________________________
> bitbake-devel mailing list
> bitbake-devel@lists.openembedded.org
> http://lists.openembedded.org/mailman/listinfo/bitbake-devel
Richard Purdie - June 14, 2013, 1:33 p.m.
On Thu, 2013-06-13 at 16:23 +0100, Alex Damian wrote:
> On 06/12/2013 02:11 PM, Richard Purdie wrote:
> > On Fri, 2013-06-07 at 16:42 +0100, Alex DAMIAN wrote:
> >> From: Alexandru DAMIAN <alexandru.damian@intel.com>
> >>
> >> I add an observer only mode for the knotty UI and
> >> the XMLRPC server that will allow the UI to register
> >> a callback with a server in order to receive events.
> >>
> >> The observer-UI will not send any commands to the
> >> server apart from registering as an event handler.
> >>
> >> Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
> >> ---
> >>   bin/bitbake             |  8 +++++++-
> >>   lib/bb/server/xmlrpc.py | 18 ++++++++++++------
> >>   lib/bb/ui/knotty.py     | 29 +++++++++++++++++------------
> >>   lib/bb/ui/uievent.py    |  1 +
> >>   4 files changed, 37 insertions(+), 19 deletions(-)
> >>

You responded to one of my comments but not the other.

To put my concern succinctly, I think even in observer mode the UIs
should still be able to query variables. I do not think they should be
writing to things though, or running any of the async commands. The
whole token business isn't working quite right yet.

My worry is the architecture you're building here isn't quite right, its
limiting the clients unnecessarily and complicating the handover between
clients. I appreciate this patch in itself isn't too problematic,
however if I merge it, you will continue down this path and we will end
up adding code that is problematic. I therefore don't think I can take
this patch until we better figure out what we need to do with the token
handling, I do know we can do better than what is here.

Cheers,

Richard

Patch

diff --git a/bin/bitbake b/bin/bitbake
index d263cbd..ef0c5d8 100755
--- a/bin/bitbake
+++ b/bin/bitbake
@@ -197,6 +197,9 @@  class BitBakeConfigParameters(cookerdata.ConfigParameters):
         parser.add_option("", "--remote-server", help = "Connect to the specified server",
                    action = "store", dest = "remote_server", default = False)
 
+        parser.add_option("", "--observe-only", help = "Connect to a server as an observing-only client",
+                   action = "store_true", dest = "observe_only", default = False)
+
         options, targets = parser.parse_args(sys.argv)
         return options, targets[1:]
 
@@ -269,6 +272,9 @@  def main():
     if configParams.remote_server and configParams.servertype != "xmlrpc":
         sys.exit("FATAL: If '--remote-server' is defined, we must set the servertype as 'xmlrpc'.\n")
 
+    if configParams.observe_only and (not configParams.remote_server or configParams.bind):
+        sys.exit("FATAL: '--observe-only' can only be used by UI clients connecting to a server.\n")
+
     if "BBDEBUG" in os.environ:
         level = int(os.environ["BBDEBUG"])
         if level > configuration.debug:
@@ -295,7 +301,7 @@  def main():
         server = start_server(servermodule, configParams, configuration)
     else:
         # we start a stub server that is actually a XMLRPClient to
-        server = servermodule.BitBakeXMLRPCClient()
+        server = servermodule.BitBakeXMLRPCClient(configParams.observe_only)
         server.saveConnectionDetails(configParams.remote_server)
 
     logger.removeHandler(handler)
diff --git a/lib/bb/server/xmlrpc.py b/lib/bb/server/xmlrpc.py
index 0b51ebd..1b3502e 100644
--- a/lib/bb/server/xmlrpc.py
+++ b/lib/bb/server/xmlrpc.py
@@ -214,7 +214,7 @@  class BitBakeXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
             remote_token = self.headers["Bitbake-token"]
         except:
             remote_token = None
-        if remote_token != self.connection_token:
+        if remote_token != self.connection_token and remote_token != "observer":
             self.report_503()
         else:
             SimpleXMLRPCRequestHandler.do_POST(self)
@@ -424,13 +424,17 @@  class XMLRPCServer(SimpleXMLRPCServer, BaseImplServer):
         self.connection_token = token
 
 class BitBakeXMLRPCServerConnection(BitBakeBaseServerConnection):
-    def __init__(self, serverImpl, clientinfo=("localhost", 0)):
+    def __init__(self, serverImpl, clientinfo=("localhost", 0), observer_only = False):
         self.connection, self.transport = _create_server(serverImpl.host, serverImpl.port)
         self.clientinfo = clientinfo
         self.serverImpl = serverImpl
+        self.observer_only = observer_only
 
     def connect(self):
-        token = self.connection.addClient()
+        if not self.observer_only:
+            token = self.connection.addClient()
+        else:
+            token = "observer"
         if token is None:
             return None
         self.transport.set_connection_token(token)
@@ -440,7 +444,8 @@  class BitBakeXMLRPCServerConnection(BitBakeBaseServerConnection):
         return self
 
     def removeClient(self):
-        self.connection.removeClient()
+        if not self.observer_only:
+            self.connection.removeClient()
 
     def terminate(self):
         # Don't wait for server indefinitely
@@ -472,7 +477,8 @@  class BitBakeServer(BitBakeBaseServer):
 
 class BitBakeXMLRPCClient(BitBakeBaseServer):
 
-    def __init__(self):
+    def __init__(self, observer_only = False):
+        self.observer_only = observer_only
         pass
 
     def saveConnectionDetails(self, remote):
@@ -495,7 +501,7 @@  class BitBakeXMLRPCClient(BitBakeBaseServer):
         except:
             return None
         self.serverImpl = XMLRPCProxyServer(host, port)
-        self.connection = BitBakeXMLRPCServerConnection(self.serverImpl, (ip, 0))
+        self.connection = BitBakeXMLRPCServerConnection(self.serverImpl, (ip, 0), self.observer_only)
         return self.connection.connect()
 
     def endSession(self):
diff --git a/lib/bb/ui/knotty.py b/lib/bb/ui/knotty.py
index 389c3cc..465203f 100644
--- a/lib/bb/ui/knotty.py
+++ b/lib/bb/ui/knotty.py
@@ -216,21 +216,28 @@  class TerminalFilter(object):
             fd = sys.stdin.fileno()
             self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
 
-def main(server, eventHandler, params, tf = TerminalFilter):
-
+def _log_settings_from_server(server):
     # Get values of variables which control our output
     includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
     if error:
         logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
-        return 1
+        raise error
     loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
     if error:
         logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
-        return 1
+        raise error
     consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
     if error:
         logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
-        return 1
+        raise error
+    return includelogs, loglines, consolelogfile
+
+def main(server, eventHandler, params, tf = TerminalFilter):
+
+    if params.observe_only:
+        includelogs, loglines, consolelogfile = None, None, None
+    else:
+        includelogs, loglines, consolelogfile = _log_settings_from_server(server)
 
     if sys.stdin.isatty() and sys.stdout.isatty():
         log_exec_tty = True
@@ -254,7 +261,7 @@  def main(server, eventHandler, params, tf = TerminalFilter):
         consolelog.setFormatter(conlogformat)
         logger.addHandler(consolelog)
 
-    try:
+    if not params.observe_only:
         params.updateFromServer(server)
         cmdline = params.parseActions()
         if not cmdline:
@@ -271,9 +278,7 @@  def main(server, eventHandler, params, tf = TerminalFilter):
         elif ret != True:
             logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
             return 1
-    except xmlrpclib.Fault as x:
-        logger.error("XMLRPC Fault getting commandline:\n %s" % x)
-        return 1
+
 
     parseprogress = None
     cacheprogress = None
@@ -320,7 +325,7 @@  def main(server, eventHandler, params, tf = TerminalFilter):
                 elif event.levelno == format.WARNING:
                     warnings = warnings + 1
                 # For "normal" logging conditions, don't show note logs from tasks
-                # but do show them if the user has changed the default log level to 
+                # but do show them if the user has changed the default log level to
                 # include verbose/debug messages
                 if event.taskpid != 0 and event.levelno <= format.NOTE:
                     continue
@@ -469,12 +474,12 @@  def main(server, eventHandler, params, tf = TerminalFilter):
                 pass
         except KeyboardInterrupt:
             termfilter.clearFooter()
-            if main.shutdown == 1:
+            if not params.observe_only and main.shutdown == 1:
                 print("\nSecond Keyboard Interrupt, stopping...\n")
                 _, error = server.runCommand(["stateStop"])
                 if error:
                     logger.error("Unable to cleanly stop: %s" % error)
-            if main.shutdown == 0:
+            if not params.observe_only and main.shutdown == 0:
                 print("\nKeyboard Interrupt, closing down...\n")
                 interrupted = True
                 _, error = server.runCommand(["stateShutdown"])
diff --git a/lib/bb/ui/uievent.py b/lib/bb/ui/uievent.py
index 0b9a836..038029f 100644
--- a/lib/bb/ui/uievent.py
+++ b/lib/bb/ui/uievent.py
@@ -84,6 +84,7 @@  class BBUIEventQueue:
 
     def startCallbackHandler(self):
 
+        self.server.timeout = 1
         while not self.server.quit:
             self.server.handle_request()
         self.server.server_close()