You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by tr...@apache.org on 2013/10/07 23:20:20 UTC

svn commit: r1530071 - in /qpid/trunk/qpid/extras/dispatch: config.sh src/router_node.c tools/ tools/src/ tools/src/py/ tools/src/py/qdstat tools/src/py/qdtoollibs/ tools/src/py/qdtoollibs/__init__.py tools/src/py/qdtoollibs/disp.py

Author: tross
Date: Mon Oct  7 21:20:20 2013
New Revision: 1530071

URL: http://svn.apache.org/r1530071
Log:
QPID-5213 - Added a CLI tool to access management data in the router.

Added:
    qpid/trunk/qpid/extras/dispatch/tools/
    qpid/trunk/qpid/extras/dispatch/tools/src/
    qpid/trunk/qpid/extras/dispatch/tools/src/py/
    qpid/trunk/qpid/extras/dispatch/tools/src/py/qdstat   (with props)
    qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/
    qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/__init__.py   (with props)
    qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/disp.py   (with props)
Modified:
    qpid/trunk/qpid/extras/dispatch/config.sh
    qpid/trunk/qpid/extras/dispatch/src/router_node.c

Modified: qpid/trunk/qpid/extras/dispatch/config.sh
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/config.sh?rev=1530071&r1=1530070&r2=1530071&view=diff
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/config.sh (original)
+++ qpid/trunk/qpid/extras/dispatch/config.sh Mon Oct  7 21:20:20 2013
@@ -22,5 +22,6 @@ cd $(dirname ${BASH_SOURCE[0]}) > /dev/n
 export QPID_DISPATCH_HOME=$(pwd)
 cd - > /dev/null
 
-export PYTHONPATH=$QPID_DISPATCH_HOME/python:$PYTHONPATH
+export PYTHONPATH=$QPID_DISPATCH_HOME/python:$QPID_DISPATCH_HOME/tools/src/py:$PYTHONPATH
+export PATH=$QPID_DISPATCH_HOME/tools/src/py:$PATH
 

Modified: qpid/trunk/qpid/extras/dispatch/src/router_node.c
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/router_node.c?rev=1530071&r1=1530070&r2=1530071&view=diff
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/router_node.c (original)
+++ qpid/trunk/qpid/extras/dispatch/src/router_node.c Mon Oct  7 21:20:20 2013
@@ -429,6 +429,12 @@ static void router_rx_handler(void* cont
 
             if (addr) {
                 //
+                // If the incoming link is an endpoint link, count this as an ingress delivery.
+                //
+                if (rlink->link_type == DX_LINK_ENDPOINT)
+                    addr->deliveries_ingress++;
+
+                //
                 // To field is valid and contains a known destination.  Handle the various
                 // cases for forwarding.
                 //
@@ -678,6 +684,7 @@ static int router_outgoing_link_handler(
     const char  *r_src   = pn_terminus_get_address(dx_link_remote_source(link));
     int is_dynamic       = pn_terminus_is_dynamic(dx_link_remote_source(link));
     int is_router        = dx_router_terminus_is_router(dx_link_remote_target(link));
+    dx_field_iterator_t *iter = 0;
 
     //
     // If this link is not a router link and it has no source address, we can't
@@ -688,6 +695,25 @@ static int router_outgoing_link_handler(
         return 0;
     }
 
+
+    //
+    // If this is an endpoint link with a source address, make sure the address is
+    // appropriate for endpoint links.  If it is not a local or mobile address, (i.e.
+    // a router or area address), it cannot be bound to an endpoint link.
+    //
+    if(r_src && !is_router && !is_dynamic) {
+        iter = dx_field_iterator_string(r_src, ITER_VIEW_ADDRESS_HASH);
+        unsigned char prefix = dx_field_iterator_octet(iter);
+        dx_field_iterator_reset(iter);
+
+        if (prefix != 'L' && prefix != 'M') {
+            dx_field_iterator_free(iter);
+            pn_link_close(pn_link);
+            dx_log(module, LOG_WARNING, "Rejected an outgoing endpoint link with a router address: %s", r_src);
+            return 0;
+        }
+    }
+
     //
     // Create a router_link record for this link.  Some of the fields will be
     // modified in the different cases below.
@@ -725,19 +751,16 @@ static int router_outgoing_link_handler(
         // assign it an ephemeral and routable address.  If it has a non-dymanic
         // address, that address needs to be set up in the address list.
         //
-        dx_field_iterator_t *iter;
-        char                 temp_addr[1000]; // FIXME
-        dx_address_t        *addr;
+        char          temp_addr[1000]; // FIXME
+        dx_address_t *addr;
 
         if (is_dynamic) {
             dx_router_generate_temp_addr(router, temp_addr, 1000);
             iter = dx_field_iterator_string(temp_addr, ITER_VIEW_ADDRESS_HASH);
             pn_terminus_set_address(dx_link_source(link), temp_addr);
             dx_log(module, LOG_INFO, "Assigned temporary routable address: %s", temp_addr);
-        } else {
-            iter = dx_field_iterator_string(r_src, ITER_VIEW_ADDRESS_HASH);
+        } else
             dx_log(module, LOG_INFO, "Registered local address: %s", r_src);
-        }
 
         hash_retrieve(router->addr_hash, iter, (void**) &addr);
         if (!addr) {

Added: qpid/trunk/qpid/extras/dispatch/tools/src/py/qdstat
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/tools/src/py/qdstat?rev=1530071&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/tools/src/py/qdstat (added)
+++ qpid/trunk/qpid/extras/dispatch/tools/src/py/qdstat Mon Oct  7 21:20:20 2013
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import os
+from optparse import OptionParser, OptionGroup
+import sys
+import locale
+import socket
+import re
+from proton import Messenger, Message
+
+home = os.environ.get("QD_TOOLS_HOME", os.path.normpath("/usr/share/qd-tools"))
+sys.path.append(os.path.join(home, "python"))
+
+from qdtoollibs import Display, Header, Sorter, YN, Commas, TimeLong
+
+
+class Config:
+    def __init__(self):
+        self._host = "0.0.0.0"
+        self._connTimeout = 10
+        self._types = ""
+        self._limit = 50
+        self._increasing = False
+        self._sortcol = None
+
+config = Config()
+conn_options = {}
+
+def OptionsAndArguments(argv):
+    """ Set global variables for options, return arguments """
+
+    global config
+    global conn_options
+
+    usage = \
+"""%prog -g [options]
+       %prog -c [options]
+       %prog -l [options]
+       %prog -n [options]
+       %prog -a [options]
+       %prog -m [options]"""
+
+    parser = OptionParser(usage=usage)
+
+    group1 = OptionGroup(parser, "General Options")
+    group1.add_option("-b", "--bus",  action="store", type="string", default="0.0.0.0", metavar="<url>",
+                      help="URL of the messaging bus to connect to")
+    group1.add_option("-t", "--timeout", action="store", type="int", default=10, metavar="<secs>",
+                      help="Maximum time to wait for connection (in seconds)")
+    group1.add_option("--sasl-mechanism", action="store", type="string", metavar="<mech>",
+                      help="SASL mechanism for authentication (e.g. EXTERNAL, ANONYMOUS, PLAIN, CRAM-MD5, DIGEST-MD5, GSSAPI). SASL automatically picks the most secure available mechanism - use this option to override.")
+    group1.add_option("--ssl-certificate", action="store", type="string", metavar="<cert>", help="Client SSL certificate (PEM Format)")
+    group1.add_option("--ssl-key", action="store", type="string", metavar="<key>", help="Client SSL private key (PEM Format)")
+    parser.add_option_group(group1)
+
+    group2 = OptionGroup(parser, "Command Options")
+    group2.add_option("-g", "--general", help="Show General Broker Stats",  action="store_const", const="g",   dest="show")
+    group2.add_option("-c", "--connections", help="Show Connections",       action="store_const", const="c",   dest="show")
+    group2.add_option("-l", "--links", help="Show Router Links",            action="store_const", const="l",   dest="show")
+    group2.add_option("-n", "--nodes", help="Show Router Nodes",            action="store_const", const="n",   dest="show")
+    group2.add_option("-a", "--address", help="Show Router Addresses",      action="store_const", const="a",   dest="show")
+    group2.add_option("-m", "--memory", help="Show Broker Memory Stats",    action="store_const", const="m",   dest="show")
+    parser.add_option_group(group2)
+
+    opts, args = parser.parse_args(args=argv)
+
+    if not opts.show:
+        parser.error("You must specify one of these options: -g, -c, -l, -n, -a, or -m. For details, try $ qdstat --help")
+
+    config._types = opts.show
+    config._host = opts.bus
+    config._connTimeout = opts.timeout
+
+    return args
+
+
+class BusManager:
+    def __init__(self):
+        pass
+
+    def SetHost(self, host):
+        self.M = Messenger()
+        self.M.start()
+        self.M.route("amqp:/*", "amqp://%s/$1" % host)
+        self.address = "amqp:/_local/agent"
+        self.reply   = "amqp:/reply-address/0001"    # FIX THIS!
+        self.M.subscribe(self.reply)
+
+    def Disconnect(self):
+        self.M.stop()
+
+    def displayConn(self):
+        pass
+
+    def _addr_class(self, addr):
+        if not addr:
+            return "-"
+        if addr[0] == 'M' : return "global"
+        if addr[0] == 'R' : return "router"
+        if addr[0] == 'A' : return "area"
+        if addr[0] == 'L' : return "local"
+        return "unknown: %s" % addr[0]
+
+    def _addr_text(self, addr):
+        if not addr:
+            return "-"
+        return addr[1:]
+
+    def displayRouterLinks(self):
+        disp = Display(prefix="  ")
+        heads = []
+        heads.append(Header("type"))
+        heads.append(Header("dir"))
+        heads.append(Header("rindex"))
+        heads.append(Header("class"))
+        heads.append(Header("addr"))
+        rows = []
+
+        request = Message()
+        response = Message()
+
+        request.address = self.address
+        request.reply_to = self.reply
+        request.correlation_id = 1
+        request.properties = {u'operation':u'GET', u'type':u'org.apache.qpid.dispatch.router.link'}
+
+        self.M.put(request)
+        self.M.send()
+
+        self.M.recv()
+        self.M.get(response)
+
+        for link in response.body:
+            row = []
+            row.append(link['link-type'])
+            row.append(link['link-dir'])
+            if link['link-type'] == "router":
+                row.append(link['index'])
+            else:
+                row.append('-')
+            row.append(self._addr_class(link['owning-addr']))
+            row.append(self._addr_text(link['owning-addr']))
+            rows.append(row)
+        title = "Router Links"
+        dispRows = rows
+        disp.formattedTable(title, heads, dispRows)
+
+    def displayAddresses(self):
+        disp = Display(prefix="  ")
+        heads = []
+        heads.append(Header("class"))
+        heads.append(Header("address"))
+        heads.append(Header("in-proc", Header.Y))
+        heads.append(Header("local", Header.COMMAS))
+        heads.append(Header("remote", Header.COMMAS))
+        heads.append(Header("in", Header.COMMAS))
+        heads.append(Header("out", Header.COMMAS))
+        heads.append(Header("thru", Header.COMMAS))
+        rows = []
+
+        request = Message()
+        response = Message()
+
+        request.address = self.address
+        request.reply_to = self.reply
+        request.correlation_id = 1
+        request.properties = {u'operation':u'GET', u'type':u'org.apache.qpid.dispatch.router.address'}
+
+        self.M.put(request)
+        self.M.send()
+
+        self.M.recv()
+        self.M.get(response)
+
+        for addr in response.body:
+            row = []
+            row.append(self._addr_class(addr['addr']))
+            row.append(self._addr_text(addr['addr']))
+            row.append(addr['in-process'])
+            row.append(addr['subscriber-count'])
+            row.append(addr['remote-count'])
+            row.append(addr['deliveries-ingress'])
+            row.append(addr['deliveries-egress'])
+            row.append(addr['deliveries-transit'])
+            rows.append(row)
+        title = "Router Addresses"
+        sorter = Sorter(heads, rows, 'address', 0, True)
+        dispRows = sorter.getSorted()
+        disp.formattedTable(title, heads, dispRows)
+
+    def displayMemory(self):
+        disp = Display(prefix="  ")
+        heads = []
+        heads.append(Header("type"))
+        heads.append(Header("size", Header.COMMAS))
+        heads.append(Header("batch"))
+        heads.append(Header("thread-max", Header.COMMAS))
+        heads.append(Header("total", Header.COMMAS))
+        heads.append(Header("in-threads", Header.COMMAS))
+        heads.append(Header("rebal-in", Header.COMMAS))
+        heads.append(Header("rebal-out", Header.COMMAS))
+        rows = []
+
+        request = Message()
+        response = Message()
+
+        request.address = self.address
+        request.reply_to = self.reply
+        request.correlation_id = 1
+        request.properties = {u'operation':u'GET', u'type':u'org.apache.qpid.dispatch.allocator'}
+
+        self.M.put(request)
+        self.M.send()
+
+        self.M.recv()
+        self.M.get(response)
+
+        for t in response.body:
+            row = []
+            row.append(t['name'])
+            row.append(t['type_size'])
+            row.append(t['transfer_batch_size'])
+            row.append(t['local_free_list_max'])
+            row.append(t['total_alloc_from_heap'])
+            row.append(t['held_by_threads'])
+            row.append(t['batches_rebalanced_to_threads'])
+            row.append(t['batches_rebalanced_to_global'])
+            rows.append(row)
+        title = "Types"
+        sorter = Sorter(heads, rows, 'type', 0, True)
+        dispRows = sorter.getSorted()
+        disp.formattedTable(title, heads, dispRows)
+
+    def displayMain(self, names, main):
+        if   main == 'l': self.displayRouterLinks()
+        elif main == 'n': self.displayRouterNodes()
+        elif main == 'a': self.displayAddresses()
+        elif main == 'm': self.displayMemory()
+
+    def display(self, names):
+        self.displayMain(names, config._types)
+
+
+def main(argv=None):
+
+    args = OptionsAndArguments(argv)
+    bm   = BusManager()
+
+    try:
+        bm.SetHost(config._host)
+        bm.display(args)
+        bm.Disconnect()
+        return 0
+    except KeyboardInterrupt:
+        print
+    except Exception,e:
+        print "Failed: %s - %s" % (e.__class__.__name__, e)
+        raise
+
+    bm.Disconnect()   # try to deallocate brokers
+    return 1
+
+if __name__ == "__main__":
+        sys.exit(main())

Propchange: qpid/trunk/qpid/extras/dispatch/tools/src/py/qdstat
------------------------------------------------------------------------------
    svn:executable = *

Added: qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/__init__.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/__init__.py?rev=1530071&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/__init__.py (added)
+++ qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/__init__.py Mon Oct  7 21:20:20 2013
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from qdtoollibs.disp import *
+

Propchange: qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/__init__.py
------------------------------------------------------------------------------
    svn:eol-style = native

Added: qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/disp.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/disp.py?rev=1530071&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/disp.py (added)
+++ qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/disp.py Mon Oct  7 21:20:20 2013
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from time import strftime, gmtime
+
+def YN(val):
+  if val:
+    return 'Y'
+  return 'N'
+
+def Commas(value):
+  sval = str(value)
+  result = ""
+  while True:
+    if len(sval) == 0:
+      return result
+    left = sval[:-3]
+    right = sval[-3:]
+    result = right + result
+    if len(left) > 0:
+      result = ',' + result
+    sval = left
+
+def TimeLong(value):
+  return strftime("%c", gmtime(value / 1000000000))
+
+def TimeShort(value):
+  return strftime("%X", gmtime(value / 1000000000))
+
+
+class Header:
+  """ """
+  NONE = 1
+  KMG = 2
+  YN = 3
+  Y = 4
+  TIME_LONG = 5
+  TIME_SHORT = 6
+  DURATION = 7
+  COMMAS = 8
+
+  def __init__(self, text, format=NONE):
+    self.text = text
+    self.format = format
+
+  def __repr__(self):
+    return self.text
+
+  def __str__(self):
+    return self.text
+
+  def formatted(self, value):
+    try:
+      if value == None:
+        return ''
+      if self.format == Header.NONE:
+        return value
+      if self.format == Header.KMG:
+        return self.num(value)
+      if self.format == Header.YN:
+        if value:
+          return 'Y'
+        return 'N'
+      if self.format == Header.Y:
+        if value:
+          return 'Y'
+        return ''
+      if self.format == Header.TIME_LONG:
+         return TimeLong(value)
+      if self.format == Header.TIME_SHORT:
+         return TimeShort(value)
+      if self.format == Header.DURATION:
+        if value < 0: value = 0
+        sec = value / 1000000000
+        min = sec / 60
+        hour = min / 60
+        day = hour / 24
+        result = ""
+        if day > 0:
+          result = "%dd " % day
+        if hour > 0 or result != "":
+          result += "%dh " % (hour % 24)
+        if min > 0 or result != "":
+          result += "%dm " % (min % 60)
+        result += "%ds" % (sec % 60)
+        return result
+      if self.format == Header.COMMAS:
+        return Commas(value)
+    except:
+      return "?"
+
+  def numCell(self, value, tag):
+    fp = float(value) / 1000.
+    if fp < 10.0:
+      return "%1.2f%c" % (fp, tag)
+    if fp < 100.0:
+      return "%2.1f%c" % (fp, tag)
+    return "%4d%c" % (value / 1000, tag)
+
+  def num(self, value):
+    if value < 1000:
+      return "%4d" % value
+    if value < 1000000:
+      return self.numCell(value, 'k')
+    value /= 1000
+    if value < 1000000:
+      return self.numCell(value, 'm')
+    value /= 1000
+    return self.numCell(value, 'g')
+
+
+class Display:
+  """ Display formatting """
+  
+  def __init__(self, spacing=2, prefix="    "):
+    self.tableSpacing    = spacing
+    self.tablePrefix     = prefix
+    self.timestampFormat = "%X"
+
+  def formattedTable(self, title, heads, rows):
+    fRows = []
+    for row in rows:
+      fRow = []
+      col = 0
+      for cell in row:
+        fRow.append(heads[col].formatted(cell))
+        col += 1
+      fRows.append(fRow)
+    headtext = []
+    for head in heads:
+      headtext.append(head.text)
+    self.table(title, headtext, fRows)
+
+  def table(self, title, heads, rows):
+    """ Print a table with autosized columns """
+
+    # Pad the rows to the number of heads
+    for row in rows:
+      diff = len(heads) - len(row)
+      for idx in range(diff):
+        row.append("")
+
+    print title
+    if len (rows) == 0:
+      return
+    colWidth = []
+    col      = 0
+    line     = self.tablePrefix
+    for head in heads:
+      width = len (head)
+      for row in rows:
+        text = row[col]
+        if text.__class__ == str:
+          text = text.decode('utf-8')
+        cellWidth = len(unicode(text))
+        if cellWidth > width:
+          width = cellWidth
+      colWidth.append (width + self.tableSpacing)
+      line = line + head
+      if col < len (heads) - 1:
+        for i in range (colWidth[col] - len (head)):
+          line = line + " "
+      col = col + 1
+    print line
+    line = self.tablePrefix
+    for width in colWidth:
+      for i in range (width):
+        line = line + "="
+    print line
+
+    for row in rows:
+      line = self.tablePrefix
+      col  = 0
+      for width in colWidth:
+        text = row[col]
+        if text.__class__ == str:
+          text = text.decode('utf-8')
+        line = line + unicode(text)
+        if col < len (heads) - 1:
+          for i in range (width - len(unicode(text))):
+            line = line + " "
+        col = col + 1
+      print line
+
+  def do_setTimeFormat (self, fmt):
+    """ Select timestamp format """
+    if fmt == "long":
+      self.timestampFormat = "%c"
+    elif fmt == "short":
+      self.timestampFormat = "%X"
+
+  def timestamp (self, nsec):
+    """ Format a nanosecond-since-the-epoch timestamp for printing """
+    return strftime (self.timestampFormat, gmtime (nsec / 1000000000))
+
+  def duration(self, nsec):
+    if nsec < 0: nsec = 0
+    sec = nsec / 1000000000
+    min = sec / 60
+    hour = min / 60
+    day = hour / 24
+    result = ""
+    if day > 0:
+      result = "%dd " % day
+    if hour > 0 or result != "":
+      result += "%dh " % (hour % 24)
+    if min > 0 or result != "":
+      result += "%dm " % (min % 60)
+    result += "%ds" % (sec % 60)
+    return result
+
+class Sortable:
+  """ """
+  def __init__(self, row, sortIndex):
+    self.row = row
+    self.sortIndex = sortIndex
+    if sortIndex >= len(row):
+      raise Exception("sort index exceeds row boundary")
+
+  def __cmp__(self, other):
+    return cmp(self.row[self.sortIndex], other.row[self.sortIndex])
+
+  def getRow(self):
+    return self.row
+
+class Sorter:
+  """ """
+  def __init__(self, heads, rows, sortCol, limit=0, inc=True):
+    col = 0
+    for head in heads:
+      if head.text == sortCol:
+        break
+      col += 1
+    if col == len(heads):
+      raise Exception("sortCol '%s', not found in headers" % sortCol)
+
+    list = []
+    for row in rows:
+      list.append(Sortable(row, col))
+    list.sort()
+    if not inc:
+      list.reverse()
+    count = 0
+    self.sorted = []
+    for row in list:
+      self.sorted.append(row.getRow())
+      count += 1
+      if count == limit:
+        break
+
+  def getSorted(self):
+    return self.sorted

Propchange: qpid/trunk/qpid/extras/dispatch/tools/src/py/qdtoollibs/disp.py
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org