diff options
Diffstat (limited to 'linden/indra/lib')
16 files changed, 1157 insertions, 741 deletions
diff --git a/linden/indra/lib/python/indra/base/lllog.py b/linden/indra/lib/python/indra/base/lllog.py index 99c50ef..1301894 100644 --- a/linden/indra/lib/python/indra/base/lllog.py +++ b/linden/indra/lib/python/indra/base/lllog.py | |||
@@ -59,7 +59,7 @@ class Logger(object): | |||
59 | return self._sequence | 59 | return self._sequence |
60 | 60 | ||
61 | def log(self, msg, llsd): | 61 | def log(self, msg, llsd): |
62 | payload = 'LLLOGMESSAGE (%d) %s %s' % (self.next(), msg, | 62 | payload = 'INFO: log: LLLOGMESSAGE (%d) %s %s' % (self.next(), msg, |
63 | format_notation(llsd)) | 63 | format_notation(llsd)) |
64 | syslog.syslog(payload) | 64 | syslog.syslog(payload) |
65 | 65 | ||
diff --git a/linden/indra/lib/python/indra/base/llsd.py b/linden/indra/lib/python/indra/base/llsd.py index 995ace7..5b8f5d7 100644 --- a/linden/indra/lib/python/indra/base/llsd.py +++ b/linden/indra/lib/python/indra/base/llsd.py | |||
@@ -33,19 +33,26 @@ import time | |||
33 | import types | 33 | import types |
34 | import re | 34 | import re |
35 | 35 | ||
36 | from indra.util.fastest_elementtree import fromstring | 36 | from indra.util.fastest_elementtree import ElementTreeError, fromstring |
37 | from indra.base import lluuid | 37 | from indra.base import lluuid |
38 | 38 | ||
39 | int_regex = re.compile("[-+]?\d+") | 39 | try: |
40 | real_regex = re.compile("[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?") | 40 | import cllsd |
41 | alpha_regex = re.compile("[a-zA-Z]+") | 41 | except ImportError: |
42 | date_regex = re.compile("(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?P<second_float>\.\d{2})?Z") | 42 | cllsd = None |
43 | #date: d"YYYY-MM-DDTHH:MM:SS.FFZ" | 43 | |
44 | int_regex = re.compile(r"[-+]?\d+") | ||
45 | real_regex = re.compile(r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?") | ||
46 | alpha_regex = re.compile(r"[a-zA-Z]+") | ||
47 | date_regex = re.compile(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T" | ||
48 | r"(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})" | ||
49 | r"(?P<second_float>(\.\d+)?)Z") | ||
50 | #date: d"YYYY-MM-DDTHH:MM:SS.FFFFFFZ" | ||
44 | 51 | ||
45 | class LLSDParseError(Exception): | 52 | class LLSDParseError(Exception): |
46 | pass | 53 | pass |
47 | 54 | ||
48 | class LLSDSerializationError(Exception): | 55 | class LLSDSerializationError(TypeError): |
49 | pass | 56 | pass |
50 | 57 | ||
51 | 58 | ||
@@ -62,14 +69,7 @@ BOOL_FALSE = ('0', '0.0', 'false', '') | |||
62 | 69 | ||
63 | def format_datestr(v): | 70 | def format_datestr(v): |
64 | """ Formats a datetime object into the string format shared by xml and notation serializations.""" | 71 | """ Formats a datetime object into the string format shared by xml and notation serializations.""" |
65 | second_str = "" | 72 | return v.isoformat() + 'Z' |
66 | if v.microsecond > 0: | ||
67 | seconds = v.second + float(v.microsecond) / 1000000 | ||
68 | second_str = "%05.2f" % seconds | ||
69 | else: | ||
70 | second_str = "%d" % v.second | ||
71 | return '%s%sZ' % (v.strftime('%Y-%m-%dT%H:%M:'), second_str) | ||
72 | |||
73 | 73 | ||
74 | def parse_datestr(datestr): | 74 | def parse_datestr(datestr): |
75 | """Parses a datetime object from the string format shared by xml and notation serializations.""" | 75 | """Parses a datetime object from the string format shared by xml and notation serializations.""" |
@@ -89,7 +89,7 @@ def parse_datestr(datestr): | |||
89 | seconds_float = match.group('second_float') | 89 | seconds_float = match.group('second_float') |
90 | microsecond = 0 | 90 | microsecond = 0 |
91 | if seconds_float: | 91 | if seconds_float: |
92 | microsecond = int(seconds_float[1:]) * 10000 | 92 | microsecond = int(float('0' + seconds_float) * 1e6) |
93 | return datetime.datetime(year, month, day, hour, minute, second, microsecond) | 93 | return datetime.datetime(year, month, day, hour, minute, second, microsecond) |
94 | 94 | ||
95 | 95 | ||
@@ -116,7 +116,7 @@ def uuid_to_python(node): | |||
116 | return lluuid.UUID(node.text) | 116 | return lluuid.UUID(node.text) |
117 | 117 | ||
118 | def str_to_python(node): | 118 | def str_to_python(node): |
119 | return unicode(node.text or '').encode('utf8', 'replace') | 119 | return node.text or '' |
120 | 120 | ||
121 | def bin_to_python(node): | 121 | def bin_to_python(node): |
122 | return binary(base64.decodestring(node.text or '')) | 122 | return binary(base64.decodestring(node.text or '')) |
@@ -189,9 +189,13 @@ class LLSDXMLFormatter(object): | |||
189 | if(contents is None or contents is ''): | 189 | if(contents is None or contents is ''): |
190 | return "<%s />" % (name,) | 190 | return "<%s />" % (name,) |
191 | else: | 191 | else: |
192 | if type(contents) is unicode: | ||
193 | contents = contents.encode('utf-8') | ||
192 | return "<%s>%s</%s>" % (name, contents, name) | 194 | return "<%s>%s</%s>" % (name, contents, name) |
193 | 195 | ||
194 | def xml_esc(self, v): | 196 | def xml_esc(self, v): |
197 | if type(v) is unicode: | ||
198 | v = v.encode('utf-8') | ||
195 | return v.replace('&', '&').replace('<', '<').replace('>', '>') | 199 | return v.replace('&', '&').replace('<', '<').replace('>', '>') |
196 | 200 | ||
197 | def LLSD(self, v): | 201 | def LLSD(self, v): |
@@ -237,9 +241,14 @@ class LLSDXMLFormatter(object): | |||
237 | raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( | 241 | raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( |
238 | t, something)) | 242 | t, something)) |
239 | 243 | ||
240 | def format(self, something): | 244 | def _format(self, something): |
241 | return '<?xml version="1.0" ?>' + self.elt("llsd", self.generate(something)) | 245 | return '<?xml version="1.0" ?>' + self.elt("llsd", self.generate(something)) |
242 | 246 | ||
247 | def format(self, something): | ||
248 | if cllsd: | ||
249 | return cllsd.llsd_to_xml(something) | ||
250 | return self._format(something) | ||
251 | |||
243 | _g_xml_formatter = None | 252 | _g_xml_formatter = None |
244 | def format_xml(something): | 253 | def format_xml(something): |
245 | global _g_xml_formatter | 254 | global _g_xml_formatter |
@@ -356,8 +365,10 @@ class LLSDNotationFormatter(object): | |||
356 | def UUID(self, v): | 365 | def UUID(self, v): |
357 | return "u%s" % v | 366 | return "u%s" % v |
358 | def BINARY(self, v): | 367 | def BINARY(self, v): |
359 | raise LLSDSerializationError("binary notation not yet supported") | 368 | return 'b64"' + base64.encodestring(v) + '"' |
360 | def STRING(self, v): | 369 | def STRING(self, v): |
370 | if isinstance(v, unicode): | ||
371 | v = v.encode('utf-8') | ||
361 | return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'") | 372 | return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'") |
362 | def URI(self, v): | 373 | def URI(self, v): |
363 | return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') | 374 | return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') |
@@ -366,16 +377,24 @@ class LLSDNotationFormatter(object): | |||
366 | def ARRAY(self, v): | 377 | def ARRAY(self, v): |
367 | return "[%s]" % ','.join([self.generate(item) for item in v]) | 378 | return "[%s]" % ','.join([self.generate(item) for item in v]) |
368 | def MAP(self, v): | 379 | def MAP(self, v): |
369 | return "{%s}" % ','.join(["'%s':%s" % (key.replace("\\", "\\\\").replace("'", "\\'"), self.generate(value)) | 380 | def fix(key): |
381 | if isinstance(key, unicode): | ||
382 | return key.encode('utf-8') | ||
383 | return key | ||
384 | return "{%s}" % ','.join(["'%s':%s" % (fix(key).replace("\\", "\\\\").replace("'", "\\'"), self.generate(value)) | ||
370 | for key, value in v.items()]) | 385 | for key, value in v.items()]) |
371 | 386 | ||
372 | def generate(self, something): | 387 | def generate(self, something): |
373 | t = type(something) | 388 | t = type(something) |
374 | if self.type_map.has_key(t): | 389 | handler = self.type_map.get(t) |
375 | return self.type_map[t](something) | 390 | if handler: |
391 | return handler(something) | ||
376 | else: | 392 | else: |
377 | raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( | 393 | try: |
378 | t, something)) | 394 | return self.ARRAY(iter(something)) |
395 | except TypeError: | ||
396 | raise LLSDSerializationError( | ||
397 | "Cannot serialize unknown type: %s (%s)" % (t, something)) | ||
379 | 398 | ||
380 | def format(self, something): | 399 | def format(self, something): |
381 | return self.generate(something) | 400 | return self.generate(something) |
@@ -479,7 +498,6 @@ class LLSDBinaryParser(object): | |||
479 | raise LLSDParseError("invalid map key at byte %d." % ( | 498 | raise LLSDParseError("invalid map key at byte %d." % ( |
480 | self._index - 1,)) | 499 | self._index - 1,)) |
481 | value = self._parse() | 500 | value = self._parse() |
482 | #print "kv:",key,value | ||
483 | rv[key] = value | 501 | rv[key] = value |
484 | count += 1 | 502 | count += 1 |
485 | cc = self._buffer[self._index] | 503 | cc = self._buffer[self._index] |
@@ -636,11 +654,23 @@ class LLSDNotationParser(object): | |||
636 | # 'd' = date in seconds since epoch | 654 | # 'd' = date in seconds since epoch |
637 | return self._parse_date() | 655 | return self._parse_date() |
638 | elif cc == 'b': | 656 | elif cc == 'b': |
639 | raise LLSDParseError("binary notation not yet supported") | 657 | return self._parse_binary() |
640 | else: | 658 | else: |
641 | raise LLSDParseError("invalid token at index %d: %d" % ( | 659 | raise LLSDParseError("invalid token at index %d: %d" % ( |
642 | self._index - 1, ord(cc))) | 660 | self._index - 1, ord(cc))) |
643 | 661 | ||
662 | def _parse_binary(self): | ||
663 | i = self._index | ||
664 | if self._buffer[i:i+2] == '64': | ||
665 | q = self._buffer[i+2] | ||
666 | e = self._buffer.find(q, i+3) | ||
667 | try: | ||
668 | return base64.decodestring(self._buffer[i+3:e]) | ||
669 | finally: | ||
670 | self._index = e + 1 | ||
671 | else: | ||
672 | raise LLSDParseError('random horrible binary format not supported') | ||
673 | |||
644 | def _parse_map(self): | 674 | def _parse_map(self): |
645 | """ map: { string:object, string:object } """ | 675 | """ map: { string:object, string:object } """ |
646 | rv = {} | 676 | rv = {} |
@@ -653,30 +683,23 @@ class LLSDNotationParser(object): | |||
653 | if cc in ("'", '"', 's'): | 683 | if cc in ("'", '"', 's'): |
654 | key = self._parse_string(cc) | 684 | key = self._parse_string(cc) |
655 | found_key = True | 685 | found_key = True |
656 | #print "key:",key | ||
657 | elif cc.isspace() or cc == ',': | 686 | elif cc.isspace() or cc == ',': |
658 | cc = self._buffer[self._index] | 687 | cc = self._buffer[self._index] |
659 | self._index += 1 | 688 | self._index += 1 |
660 | else: | 689 | else: |
661 | raise LLSDParseError("invalid map key at byte %d." % ( | 690 | raise LLSDParseError("invalid map key at byte %d." % ( |
662 | self._index - 1,)) | 691 | self._index - 1,)) |
692 | elif cc.isspace() or cc == ':': | ||
693 | cc = self._buffer[self._index] | ||
694 | self._index += 1 | ||
695 | continue | ||
663 | else: | 696 | else: |
664 | if cc.isspace() or cc == ':': | ||
665 | #print "skipping whitespace '%s'" % cc | ||
666 | cc = self._buffer[self._index] | ||
667 | self._index += 1 | ||
668 | continue | ||
669 | self._index += 1 | 697 | self._index += 1 |
670 | value = self._parse() | 698 | value = self._parse() |
671 | #print "kv:",key,value | ||
672 | rv[key] = value | 699 | rv[key] = value |
673 | found_key = False | 700 | found_key = False |
674 | cc = self._buffer[self._index] | 701 | cc = self._buffer[self._index] |
675 | self._index += 1 | 702 | self._index += 1 |
676 | #if cc == '}': | ||
677 | # break | ||
678 | #cc = self._buffer[self._index] | ||
679 | #self._index += 1 | ||
680 | 703 | ||
681 | return rv | 704 | return rv |
682 | 705 | ||
@@ -840,6 +863,14 @@ def format_binary(something): | |||
840 | return '<?llsd/binary?>\n' + _format_binary_recurse(something) | 863 | return '<?llsd/binary?>\n' + _format_binary_recurse(something) |
841 | 864 | ||
842 | def _format_binary_recurse(something): | 865 | def _format_binary_recurse(something): |
866 | def _format_list(something): | ||
867 | array_builder = [] | ||
868 | array_builder.append('[' + struct.pack('!i', len(something))) | ||
869 | for item in something: | ||
870 | array_builder.append(_format_binary_recurse(item)) | ||
871 | array_builder.append(']') | ||
872 | return ''.join(array_builder) | ||
873 | |||
843 | if something is None: | 874 | if something is None: |
844 | return '!' | 875 | return '!' |
845 | elif isinstance(something, LLSD): | 876 | elif isinstance(something, LLSD): |
@@ -857,7 +888,10 @@ def _format_binary_recurse(something): | |||
857 | return 'u' + something._bits | 888 | return 'u' + something._bits |
858 | elif isinstance(something, binary): | 889 | elif isinstance(something, binary): |
859 | return 'b' + struct.pack('!i', len(something)) + something | 890 | return 'b' + struct.pack('!i', len(something)) + something |
860 | elif isinstance(something, (str, unicode)): | 891 | elif isinstance(something, str): |
892 | return 's' + struct.pack('!i', len(something)) + something | ||
893 | elif isinstance(something, unicode): | ||
894 | something = something.encode('utf-8') | ||
861 | return 's' + struct.pack('!i', len(something)) + something | 895 | return 's' + struct.pack('!i', len(something)) + something |
862 | elif isinstance(something, uri): | 896 | elif isinstance(something, uri): |
863 | return 'l' + struct.pack('!i', len(something)) + something | 897 | return 'l' + struct.pack('!i', len(something)) + something |
@@ -865,35 +899,50 @@ def _format_binary_recurse(something): | |||
865 | seconds_since_epoch = time.mktime(something.timetuple()) | 899 | seconds_since_epoch = time.mktime(something.timetuple()) |
866 | return 'd' + struct.pack('!d', seconds_since_epoch) | 900 | return 'd' + struct.pack('!d', seconds_since_epoch) |
867 | elif isinstance(something, (list, tuple)): | 901 | elif isinstance(something, (list, tuple)): |
868 | array_builder = [] | 902 | return _format_list(something) |
869 | array_builder.append('[' + struct.pack('!i', len(something))) | ||
870 | for item in something: | ||
871 | array_builder.append(_format_binary_recurse(item)) | ||
872 | array_builder.append(']') | ||
873 | return ''.join(array_builder) | ||
874 | elif isinstance(something, dict): | 903 | elif isinstance(something, dict): |
875 | map_builder = [] | 904 | map_builder = [] |
876 | map_builder.append('{' + struct.pack('!i', len(something))) | 905 | map_builder.append('{' + struct.pack('!i', len(something))) |
877 | for key, value in something.items(): | 906 | for key, value in something.items(): |
907 | if isinstance(key, unicode): | ||
908 | key = key.encode('utf-8') | ||
878 | map_builder.append('k' + struct.pack('!i', len(key)) + key) | 909 | map_builder.append('k' + struct.pack('!i', len(key)) + key) |
879 | map_builder.append(_format_binary_recurse(value)) | 910 | map_builder.append(_format_binary_recurse(value)) |
880 | map_builder.append('}') | 911 | map_builder.append('}') |
881 | return ''.join(map_builder) | 912 | return ''.join(map_builder) |
882 | else: | 913 | else: |
883 | raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( | 914 | try: |
884 | type(something), something)) | 915 | return _format_list(list(something)) |
916 | except TypeError: | ||
917 | raise LLSDSerializationError( | ||
918 | "Cannot serialize unknown type: %s (%s)" % | ||
919 | (type(something), something)) | ||
920 | |||
921 | |||
922 | def parse_binary(something): | ||
923 | header = '<?llsd/binary?>\n' | ||
924 | if not something.startswith(header): | ||
925 | raise LLSDParseError('LLSD binary encoding header not found') | ||
926 | return LLSDBinaryParser().parse(something[len(header):]) | ||
927 | |||
928 | def parse_xml(something): | ||
929 | try: | ||
930 | return to_python(fromstring(something)[0]) | ||
931 | except ElementTreeError, err: | ||
932 | raise LLSDParseError(*err.args) | ||
885 | 933 | ||
934 | def parse_notation(something): | ||
935 | return LLSDNotationParser().parse(something) | ||
886 | 936 | ||
887 | def parse(something): | 937 | def parse(something): |
888 | try: | 938 | try: |
889 | if something.startswith('<?llsd/binary?>'): | 939 | if something.startswith('<?llsd/binary?>'): |
890 | just_binary = something.split('\n', 1)[1] | 940 | return parse_binary(something) |
891 | return LLSDBinaryParser().parse(just_binary) | ||
892 | # This should be better. | 941 | # This should be better. |
893 | elif something.startswith('<'): | 942 | elif something.startswith('<'): |
894 | return to_python(fromstring(something)[0]) | 943 | return parse_xml(something) |
895 | else: | 944 | else: |
896 | return LLSDNotationParser().parse(something) | 945 | return parse_notation(something) |
897 | except KeyError, e: | 946 | except KeyError, e: |
898 | raise Exception('LLSD could not be parsed: %s' % (e,)) | 947 | raise Exception('LLSD could not be parsed: %s' % (e,)) |
899 | 948 | ||
@@ -913,6 +962,9 @@ class LLSD(object): | |||
913 | 962 | ||
914 | undef = LLSD(None) | 963 | undef = LLSD(None) |
915 | 964 | ||
965 | XML_MIME_TYPE = 'application/llsd+xml' | ||
966 | BINARY_MIME_TYPE = 'application/llsd+binary' | ||
967 | |||
916 | # register converters for llsd in mulib, if it is available | 968 | # register converters for llsd in mulib, if it is available |
917 | try: | 969 | try: |
918 | from mulib import stacked, mu | 970 | from mulib import stacked, mu |
@@ -922,7 +974,7 @@ except: | |||
922 | # mulib not available, don't print an error message since this is normal | 974 | # mulib not available, don't print an error message since this is normal |
923 | pass | 975 | pass |
924 | else: | 976 | else: |
925 | mu.add_parser(parse, 'application/llsd+xml') | 977 | mu.add_parser(parse, XML_MIME_TYPE) |
926 | mu.add_parser(parse, 'application/llsd+binary') | 978 | mu.add_parser(parse, 'application/llsd+binary') |
927 | 979 | ||
928 | def llsd_convert_xml(llsd_stuff, request): | 980 | def llsd_convert_xml(llsd_stuff, request): |
@@ -931,11 +983,58 @@ else: | |||
931 | def llsd_convert_binary(llsd_stuff, request): | 983 | def llsd_convert_binary(llsd_stuff, request): |
932 | request.write(format_binary(llsd_stuff)) | 984 | request.write(format_binary(llsd_stuff)) |
933 | 985 | ||
934 | for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]: | 986 | for typ in [LLSD, dict, list, tuple, str, int, long, float, bool, unicode, type(None)]: |
935 | stacked.add_producer(typ, llsd_convert_xml, 'application/llsd+xml') | 987 | stacked.add_producer(typ, llsd_convert_xml, XML_MIME_TYPE) |
936 | stacked.add_producer(typ, llsd_convert_xml, 'application/xml') | 988 | stacked.add_producer(typ, llsd_convert_xml, 'application/xml') |
937 | stacked.add_producer(typ, llsd_convert_xml, 'text/xml') | 989 | stacked.add_producer(typ, llsd_convert_xml, 'text/xml') |
938 | 990 | ||
939 | stacked.add_producer(typ, llsd_convert_binary, 'application/llsd+binary') | 991 | stacked.add_producer(typ, llsd_convert_binary, 'application/llsd+binary') |
940 | 992 | ||
941 | stacked.add_producer(LLSD, llsd_convert_xml, '*/*') | 993 | stacked.add_producer(LLSD, llsd_convert_xml, '*/*') |
994 | |||
995 | # in case someone is using the legacy mu.xml wrapper, we need to | ||
996 | # tell mu to produce application/xml or application/llsd+xml | ||
997 | # (based on the accept header) from raw xml. Phoenix 2008-07-21 | ||
998 | stacked.add_producer(mu.xml, mu.produce_raw, XML_MIME_TYPE) | ||
999 | stacked.add_producer(mu.xml, mu.produce_raw, 'application/xml') | ||
1000 | |||
1001 | |||
1002 | |||
1003 | # mulib wsgi stuff | ||
1004 | # try: | ||
1005 | # from mulib import mu, adapters | ||
1006 | # | ||
1007 | # # try some known attributes from mulib to be ultra-sure we've imported it | ||
1008 | # mu.get_current | ||
1009 | # adapters.handlers | ||
1010 | # except: | ||
1011 | # # mulib not available, don't print an error message since this is normal | ||
1012 | # pass | ||
1013 | # else: | ||
1014 | # def llsd_xml_handler(content_type): | ||
1015 | # def handle_llsd_xml(env, start_response): | ||
1016 | # llsd_stuff, _ = mu.get_current(env) | ||
1017 | # result = format_xml(llsd_stuff) | ||
1018 | # start_response("200 OK", [('Content-Type', content_type)]) | ||
1019 | # env['mu.negotiated_type'] = content_type | ||
1020 | # yield result | ||
1021 | # return handle_llsd_xml | ||
1022 | # | ||
1023 | # def llsd_binary_handler(content_type): | ||
1024 | # def handle_llsd_binary(env, start_response): | ||
1025 | # llsd_stuff, _ = mu.get_current(env) | ||
1026 | # result = format_binary(llsd_stuff) | ||
1027 | # start_response("200 OK", [('Content-Type', content_type)]) | ||
1028 | # env['mu.negotiated_type'] = content_type | ||
1029 | # yield result | ||
1030 | # return handle_llsd_binary | ||
1031 | # | ||
1032 | # adapters.DEFAULT_PARSERS[XML_MIME_TYPE] = parse | ||
1033 | |||
1034 | # for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]: | ||
1035 | # for content_type in (XML_MIME_TYPE, 'application/xml'): | ||
1036 | # adapters.handlers.set_handler(typ, llsd_xml_handler(content_type), content_type) | ||
1037 | # | ||
1038 | # adapters.handlers.set_handler(typ, llsd_binary_handler(BINARY_MIME_TYPE), BINARY_MIME_TYPE) | ||
1039 | # | ||
1040 | # adapters.handlers.set_handler(LLSD, llsd_xml_handler(XML_MIME_TYPE), '*/*') | ||
diff --git a/linden/indra/lib/python/indra/ipc/llmessage.py b/linden/indra/lib/python/indra/ipc/llmessage.py index 446679d..c2f3fd4 100644 --- a/linden/indra/lib/python/indra/ipc/llmessage.py +++ b/linden/indra/lib/python/indra/ipc/llmessage.py | |||
@@ -88,7 +88,7 @@ class Message: | |||
88 | UDPDEPRECATED = "UDPDeprecated" | 88 | UDPDEPRECATED = "UDPDeprecated" |
89 | UDPBLACKLISTED = "UDPBlackListed" | 89 | UDPBLACKLISTED = "UDPBlackListed" |
90 | deprecations = [ NOTDEPRECATED, UDPDEPRECATED, UDPBLACKLISTED, DEPRECATED ] | 90 | deprecations = [ NOTDEPRECATED, UDPDEPRECATED, UDPBLACKLISTED, DEPRECATED ] |
91 | # in order of increasing deprecation | 91 | # in order of increasing deprecation |
92 | 92 | ||
93 | def __init__(self, name, number, priority, trust, coding): | 93 | def __init__(self, name, number, priority, trust, coding): |
94 | self.name = name | 94 | self.name = name |
diff --git a/linden/indra/lib/python/indra/ipc/llsdhttp.py b/linden/indra/lib/python/indra/ipc/llsdhttp.py index 1cf1146..ed64899 100644 --- a/linden/indra/lib/python/indra/ipc/llsdhttp.py +++ b/linden/indra/lib/python/indra/ipc/llsdhttp.py | |||
@@ -51,6 +51,8 @@ request_ = suite.request_ | |||
51 | # import every httpc error exception into our namespace for convenience | 51 | # import every httpc error exception into our namespace for convenience |
52 | for x in httpc.status_to_error_map.itervalues(): | 52 | for x in httpc.status_to_error_map.itervalues(): |
53 | globals()[x.__name__] = x | 53 | globals()[x.__name__] = x |
54 | ConnectionError = httpc.ConnectionError | ||
55 | Retriable = httpc.Retriable | ||
54 | 56 | ||
55 | for x in (httpc.ConnectionError,): | 57 | for x in (httpc.ConnectionError,): |
56 | globals()[x.__name__] = x | 58 | globals()[x.__name__] = x |
diff --git a/linden/indra/lib/python/indra/ipc/mysql_pool.py b/linden/indra/lib/python/indra/ipc/mysql_pool.py index 507b185..25a66cf 100644 --- a/linden/indra/lib/python/indra/ipc/mysql_pool.py +++ b/linden/indra/lib/python/indra/ipc/mysql_pool.py | |||
@@ -30,8 +30,10 @@ import MySQLdb | |||
30 | from eventlet import db_pool | 30 | from eventlet import db_pool |
31 | 31 | ||
32 | class DatabaseConnector(db_pool.DatabaseConnector): | 32 | class DatabaseConnector(db_pool.DatabaseConnector): |
33 | def __init__(self, credentials, min_size = 0, max_size = 4, *args, **kwargs): | 33 | def __init__(self, credentials, *args, **kwargs): |
34 | super(DatabaseConnector, self).__init__(MySQLdb, credentials, min_size, max_size, conn_pool=db_pool.ConnectionPool, *args, **kwargs) | 34 | super(DatabaseConnector, self).__init__(MySQLdb, credentials, |
35 | conn_pool=db_pool.ConnectionPool, | ||
36 | *args, **kwargs) | ||
35 | 37 | ||
36 | # get is extended relative to eventlet.db_pool to accept a port argument | 38 | # get is extended relative to eventlet.db_pool to accept a port argument |
37 | def get(self, host, dbname, port=3306): | 39 | def get(self, host, dbname, port=3306): |
@@ -42,7 +44,7 @@ class DatabaseConnector(db_pool.DatabaseConnector): | |||
42 | new_kwargs['host'] = host | 44 | new_kwargs['host'] = host |
43 | new_kwargs['port'] = port | 45 | new_kwargs['port'] = port |
44 | new_kwargs.update(self.credentials_for(host)) | 46 | new_kwargs.update(self.credentials_for(host)) |
45 | dbpool = ConnectionPool(self._min_size, self._max_size, *self._args, **new_kwargs) | 47 | dbpool = ConnectionPool(*self._args, **new_kwargs) |
46 | self._databases[key] = dbpool | 48 | self._databases[key] = dbpool |
47 | 49 | ||
48 | return self._databases[key] | 50 | return self._databases[key] |
@@ -51,8 +53,8 @@ class ConnectionPool(db_pool.TpooledConnectionPool): | |||
51 | """A pool which gives out saranwrapped MySQLdb connections from a pool | 53 | """A pool which gives out saranwrapped MySQLdb connections from a pool |
52 | """ | 54 | """ |
53 | 55 | ||
54 | def __init__(self, min_size = 0, max_size = 4, *args, **kwargs): | 56 | def __init__(self, *args, **kwargs): |
55 | super(ConnectionPool, self).__init__(MySQLdb, min_size, max_size, *args, **kwargs) | 57 | super(ConnectionPool, self).__init__(MySQLdb, *args, **kwargs) |
56 | 58 | ||
57 | def get(self): | 59 | def get(self): |
58 | conn = super(ConnectionPool, self).get() | 60 | conn = super(ConnectionPool, self).get() |
@@ -77,14 +79,3 @@ class ConnectionPool(db_pool.TpooledConnectionPool): | |||
77 | conn.connection_parameters = converted_kwargs | 79 | conn.connection_parameters = converted_kwargs |
78 | return conn | 80 | return conn |
79 | 81 | ||
80 | def clear(self): | ||
81 | """ Close all connections that this pool still holds a reference to, leaving it empty.""" | ||
82 | for conn in self.free_items: | ||
83 | try: | ||
84 | conn.close() | ||
85 | except: | ||
86 | pass # even if stuff happens here, we still want to at least try to close all the other connections | ||
87 | self.free_items.clear() | ||
88 | |||
89 | def __del__(self): | ||
90 | self.clear() | ||
diff --git a/linden/indra/lib/python/indra/ipc/saranwrap.py b/linden/indra/lib/python/indra/ipc/saranwrap.py deleted file mode 100644 index e0205bf..0000000 --- a/linden/indra/lib/python/indra/ipc/saranwrap.py +++ /dev/null | |||
@@ -1,651 +0,0 @@ | |||
1 | """\ | ||
2 | @file saranwrap.py | ||
3 | @author Phoenix | ||
4 | @date 2007-07-13 | ||
5 | @brief A simple, pickle based rpc mechanism which reflects python | ||
6 | objects and callables. | ||
7 | |||
8 | $LicenseInfo:firstyear=2007&license=mit$ | ||
9 | |||
10 | Copyright (c) 2007-2008, Linden Research, Inc. | ||
11 | |||
12 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
13 | of this software and associated documentation files (the "Software"), to deal | ||
14 | in the Software without restriction, including without limitation the rights | ||
15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
16 | copies of the Software, and to permit persons to whom the Software is | ||
17 | furnished to do so, subject to the following conditions: | ||
18 | |||
19 | The above copyright notice and this permission notice shall be included in | ||
20 | all copies or substantial portions of the Software. | ||
21 | |||
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
28 | THE SOFTWARE. | ||
29 | $/LicenseInfo$ | ||
30 | |||
31 | This file provides classes and exceptions used for simple python level | ||
32 | remote procedure calls. This is achieved by intercepting the basic | ||
33 | getattr and setattr calls in a client proxy, which commnicates those | ||
34 | down to the server which will dispatch them to objects in it's process | ||
35 | space. | ||
36 | |||
37 | The basic protocol to get and set attributes is for the client proxy | ||
38 | to issue the command: | ||
39 | |||
40 | getattr $id $name | ||
41 | setattr $id $name $value | ||
42 | |||
43 | getitem $id $item | ||
44 | setitem $id $item $value | ||
45 | eq $id $rhs | ||
46 | del $id | ||
47 | |||
48 | When the get returns a callable, the client proxy will provide a | ||
49 | callable proxy which will invoke a remote procedure call. The command | ||
50 | issued from the callable proxy to server is: | ||
51 | |||
52 | call $id $name $args $kwargs | ||
53 | |||
54 | If the client supplies an id of None, then the get/set/call is applied | ||
55 | to the object(s) exported from the server. | ||
56 | |||
57 | The server will parse the get/set/call, take the action indicated, and | ||
58 | return back to the caller one of: | ||
59 | |||
60 | value $val | ||
61 | callable | ||
62 | object $id | ||
63 | exception $excp | ||
64 | |||
65 | To handle object expiration, the proxy will instruct the rpc server to | ||
66 | discard objects which are no longer in use. This is handled by | ||
67 | catching proxy deletion and sending the command: | ||
68 | |||
69 | del $id | ||
70 | |||
71 | The server will handle this by removing clearing it's own internal | ||
72 | references. This does not mean that the object will necessarily be | ||
73 | cleaned from the server, but no artificial references will remain | ||
74 | after successfully completing. On completion, the server will return | ||
75 | one of: | ||
76 | |||
77 | value None | ||
78 | exception $excp | ||
79 | |||
80 | The server also accepts a special command for debugging purposes: | ||
81 | |||
82 | status | ||
83 | |||
84 | Which will be intercepted by the server to write back: | ||
85 | |||
86 | status {...} | ||
87 | |||
88 | The wire protocol is to pickle the Request class in this file. The | ||
89 | request class is basically an action and a map of parameters' | ||
90 | """ | ||
91 | |||
92 | import os | ||
93 | import cPickle | ||
94 | import struct | ||
95 | import sys | ||
96 | |||
97 | try: | ||
98 | set = set | ||
99 | frozenset = frozenset | ||
100 | except NameError: | ||
101 | from sets import Set as set, ImmutableSet as frozenset | ||
102 | |||
103 | from eventlet.processes import Process | ||
104 | from eventlet import api | ||
105 | |||
106 | # | ||
107 | # debugging hooks | ||
108 | # | ||
109 | _g_debug_mode = False | ||
110 | if _g_debug_mode: | ||
111 | import traceback | ||
112 | |||
113 | def pythonpath_sync(): | ||
114 | """ | ||
115 | @brief apply the current sys.path to the environment variable PYTHONPATH, so that child processes have the same paths as the caller does. | ||
116 | """ | ||
117 | pypath = os.pathsep.join(sys.path) | ||
118 | os.environ['PYTHONPATH'] = pypath | ||
119 | |||
120 | def wrap(obj, dead_callback = None): | ||
121 | """ | ||
122 | @brief wrap in object in another process through a saranwrap proxy | ||
123 | @param object The object to wrap. | ||
124 | @param dead_callback A callable to invoke if the process exits.""" | ||
125 | |||
126 | if type(obj).__name__ == 'module': | ||
127 | return wrap_module(obj.__name__, dead_callback) | ||
128 | pythonpath_sync() | ||
129 | p = Process('python', [__file__, '--child'], dead_callback) | ||
130 | prox = Proxy(p, p) | ||
131 | prox.obj = obj | ||
132 | return prox.obj | ||
133 | |||
134 | def wrap_module(fqname, dead_callback = None): | ||
135 | """ | ||
136 | @brief wrap a module in another process through a saranwrap proxy | ||
137 | @param fqname The fully qualified name of the module. | ||
138 | @param dead_callback A callable to invoke if the process exits.""" | ||
139 | pythonpath_sync() | ||
140 | global _g_debug_mode | ||
141 | if _g_debug_mode: | ||
142 | p = Process('python', [__file__, '--module', fqname, '--logfile', '/tmp/saranwrap.log'], dead_callback) | ||
143 | else: | ||
144 | p = Process('python', [__file__, '--module', fqname,], dead_callback) | ||
145 | prox = Proxy(p, p) | ||
146 | return prox | ||
147 | |||
148 | def status(proxy): | ||
149 | """ | ||
150 | @brief get the status from the server through a proxy | ||
151 | @param proxy a saranwrap.Proxy object connected to a server.""" | ||
152 | _write_request(Request('status', {}), proxy.__local_dict['_out']) | ||
153 | return _read_response(None, None, proxy.__local_dict['_in'], proxy.__local_dict['_out'], None) | ||
154 | |||
155 | class BadResponse(Exception): | ||
156 | """"This exception is raised by an saranwrap client when it could | ||
157 | parse but cannot understand the response from the server.""" | ||
158 | pass | ||
159 | |||
160 | class BadRequest(Exception): | ||
161 | """"This exception is raised by a saranwrap server when it could parse | ||
162 | but cannot understand the response from the server.""" | ||
163 | pass | ||
164 | |||
165 | class UnrecoverableError(Exception): | ||
166 | pass | ||
167 | |||
168 | class Request(object): | ||
169 | "@brief A wrapper class for proxy requests to the server." | ||
170 | def __init__(self, action, param): | ||
171 | self._action = action | ||
172 | self._param = param | ||
173 | def __str__(self): | ||
174 | return "Request `"+self._action+"` "+str(self._param) | ||
175 | def __getitem__(self, name): | ||
176 | return self._param[name] | ||
177 | def action(self): | ||
178 | return self._action | ||
179 | |||
180 | def _read_lp_hunk(stream): | ||
181 | len_bytes = stream.read(4) | ||
182 | length = struct.unpack('I', len_bytes)[0] | ||
183 | body = stream.read(length) | ||
184 | return body | ||
185 | |||
186 | def _read_response(id, attribute, input, output, dead_list): | ||
187 | """@brief local helper method to read respones from the rpc server.""" | ||
188 | try: | ||
189 | str = _read_lp_hunk(input) | ||
190 | _prnt(`str`) | ||
191 | response = cPickle.loads(str) | ||
192 | except AttributeError, e: | ||
193 | raise UnrecoverableError(e) | ||
194 | _prnt("response: %s" % response) | ||
195 | if response[0] == 'value': | ||
196 | return response[1] | ||
197 | elif response[0] == 'callable': | ||
198 | return CallableProxy(id, attribute, input, output, dead_list) | ||
199 | elif response[0] == 'object': | ||
200 | return ObjectProxy(input, output, response[1], dead_list) | ||
201 | elif response[0] == 'exception': | ||
202 | exp = response[1] | ||
203 | raise exp | ||
204 | else: | ||
205 | raise BadResponse(response[0]) | ||
206 | |||
207 | def _write_lp_hunk(stream, hunk): | ||
208 | write_length = struct.pack('I', len(hunk)) | ||
209 | stream.write(write_length + hunk) | ||
210 | if hasattr(stream, 'flush'): | ||
211 | stream.flush() | ||
212 | |||
213 | def _write_request(param, output): | ||
214 | _prnt("request: %s" % param) | ||
215 | str = cPickle.dumps(param) | ||
216 | _write_lp_hunk(output, str) | ||
217 | |||
218 | def _is_local(attribute): | ||
219 | "Return true if the attribute should be handled locally" | ||
220 | # return attribute in ('_in', '_out', '_id', '__getattribute__', '__setattr__', '__dict__') | ||
221 | # good enough for now. :) | ||
222 | if '__local_dict' in attribute: | ||
223 | return True | ||
224 | return False | ||
225 | |||
226 | def _prnt(message): | ||
227 | global _g_debug_mode | ||
228 | if _g_debug_mode: | ||
229 | print message | ||
230 | |||
231 | _g_logfile = None | ||
232 | def _log(message): | ||
233 | global _g_logfile | ||
234 | if _g_logfile: | ||
235 | _g_logfile.write(str(os.getpid()) + ' ' + message) | ||
236 | _g_logfile.write('\n') | ||
237 | _g_logfile.flush() | ||
238 | |||
239 | def _unmunge_attr_name(name): | ||
240 | """ Sometimes attribute names come in with classname prepended, not sure why. | ||
241 | This function removes said classname, because we're huge hackers and we didn't | ||
242 | find out what the true right thing to do is. *FIX: find out. """ | ||
243 | if(name.startswith('_Proxy')): | ||
244 | name = name[len('_Proxy'):] | ||
245 | if(name.startswith('_ObjectProxy')): | ||
246 | name = name[len('_ObjectProxy'):] | ||
247 | return name | ||
248 | |||
249 | |||
250 | class Proxy(object): | ||
251 | """\ | ||
252 | @class Proxy | ||
253 | @brief This class wraps a remote python process, presumably available | ||
254 | in an instance of an Server. | ||
255 | |||
256 | This is the class you will typically use as a client to a child | ||
257 | process. Simply instantiate one around a file-like interface and start | ||
258 | calling methods on the thing that is exported. The dir() builtin is | ||
259 | not supported, so you have to know what has been exported. | ||
260 | """ | ||
261 | def __init__(self, input, output, dead_list = None): | ||
262 | """\ | ||
263 | @param input a file-like object which supports read(). | ||
264 | @param output a file-like object which supports write() and flush(). | ||
265 | @param id an identifier for the remote object. humans do not provide this. | ||
266 | """ | ||
267 | # default dead_list inside the function because all objects in method | ||
268 | # argument lists are init-ed only once globally | ||
269 | if dead_list is None: | ||
270 | dead_list = set() | ||
271 | #_prnt("Proxy::__init__") | ||
272 | self.__local_dict = dict( | ||
273 | _in = input, | ||
274 | _out = output, | ||
275 | _dead_list = dead_list, | ||
276 | _id = None) | ||
277 | |||
278 | def __getattribute__(self, attribute): | ||
279 | #_prnt("Proxy::__getattr__: %s" % attribute) | ||
280 | if _is_local(attribute): | ||
281 | # call base class getattribute so we actually get the local variable | ||
282 | attribute = _unmunge_attr_name(attribute) | ||
283 | return super(Proxy, self).__getattribute__(attribute) | ||
284 | else: | ||
285 | my_in = self.__local_dict['_in'] | ||
286 | my_out = self.__local_dict['_out'] | ||
287 | my_id = self.__local_dict['_id'] | ||
288 | |||
289 | _dead_list = self.__local_dict['_dead_list'] | ||
290 | for dead_object in _dead_list.copy(): | ||
291 | request = Request('del', {'id':dead_object}) | ||
292 | _write_request(request, my_out) | ||
293 | response = _read_response(my_id, attribute, my_in, my_out, _dead_list) | ||
294 | _dead_list.remove(dead_object) | ||
295 | |||
296 | # Pass all public attributes across to find out if it is | ||
297 | # callable or a simple attribute. | ||
298 | request = Request('getattr', {'id':my_id, 'attribute':attribute}) | ||
299 | _write_request(request, my_out) | ||
300 | return _read_response(my_id, attribute, my_in, my_out, _dead_list) | ||
301 | |||
302 | def __setattr__(self, attribute, value): | ||
303 | #_prnt("Proxy::__setattr__: %s" % attribute) | ||
304 | if _is_local(attribute): | ||
305 | # It must be local to this actual object, so we have to apply | ||
306 | # it to the dict in a roundabout way | ||
307 | attribute = _unmunge_attr_name(attribute) | ||
308 | super(Proxy, self).__getattribute__('__dict__')[attribute]=value | ||
309 | else: | ||
310 | my_in = self.__local_dict['_in'] | ||
311 | my_out = self.__local_dict['_out'] | ||
312 | my_id = self.__local_dict['_id'] | ||
313 | _dead_list = self.__local_dict['_dead_list'] | ||
314 | # Pass the set attribute across | ||
315 | request = Request('setattr', {'id':my_id, 'attribute':attribute, 'value':value}) | ||
316 | _write_request(request, my_out) | ||
317 | return _read_response(my_id, attribute, my_in, my_out, _dead_list) | ||
318 | |||
319 | class ObjectProxy(Proxy): | ||
320 | """\ | ||
321 | @class ObjectProxy | ||
322 | @brief This class wraps a remote object in the Server | ||
323 | |||
324 | This class will be created during normal operation, and users should | ||
325 | not need to deal with this class directly.""" | ||
326 | |||
327 | def __init__(self, input, output, id, dead_list): | ||
328 | """\ | ||
329 | @param input a file-like object which supports read(). | ||
330 | @param output a file-like object which supports write() and flush(). | ||
331 | @param id an identifier for the remote object. humans do not provide this. | ||
332 | """ | ||
333 | Proxy.__init__(self, input, output, dead_list) | ||
334 | self.__local_dict['_id'] = id | ||
335 | #_prnt("ObjectProxy::__init__ %s" % self._id) | ||
336 | |||
337 | def __del__(self): | ||
338 | my_id = self.__local_dict['_id'] | ||
339 | _prnt("ObjectProxy::__del__ %s" % my_id) | ||
340 | self.__local_dict['_dead_list'].add(my_id) | ||
341 | |||
342 | def __getitem__(self, key): | ||
343 | my_in = self.__local_dict['_in'] | ||
344 | my_out = self.__local_dict['_out'] | ||
345 | my_id = self.__local_dict['_id'] | ||
346 | _dead_list = self.__local_dict['_dead_list'] | ||
347 | request = Request('getitem', {'id':my_id, 'key':key}) | ||
348 | _write_request(request, my_out) | ||
349 | return _read_response(my_id, key, my_in, my_out, _dead_list) | ||
350 | |||
351 | def __setitem__(self, key, value): | ||
352 | my_in = self.__local_dict['_in'] | ||
353 | my_out = self.__local_dict['_out'] | ||
354 | my_id = self.__local_dict['_id'] | ||
355 | _dead_list = self.__local_dict['_dead_list'] | ||
356 | request = Request('setitem', {'id':my_id, 'key':key, 'value':value}) | ||
357 | _write_request(request, my_out) | ||
358 | return _read_response(my_id, key, my_in, my_out, _dead_list) | ||
359 | |||
360 | def __eq__(self, rhs): | ||
361 | my_in = self.__local_dict['_in'] | ||
362 | my_out = self.__local_dict['_out'] | ||
363 | my_id = self.__local_dict['_id'] | ||
364 | _dead_list = self.__local_dict['_dead_list'] | ||
365 | request = Request('eq', {'id':my_id, 'rhs':rhs.__local_dict['_id']}) | ||
366 | _write_request(request, my_out) | ||
367 | return _read_response(my_id, None, my_in, my_out, _dead_list) | ||
368 | |||
369 | def __repr__(self): | ||
370 | # apparently repr(obj) skips the whole getattribute thing and just calls __repr__ | ||
371 | # directly. Therefore we just pass it through the normal call pipeline, and | ||
372 | # tack on a little header so that you can tell it's an object proxy. | ||
373 | val = self.__repr__() | ||
374 | return "saran:%s" % val | ||
375 | |||
376 | def __str__(self): | ||
377 | # see description for __repr__, because str(obj) works the same. We don't | ||
378 | # tack anything on to the return value here because str values are used as data. | ||
379 | return self.__str__() | ||
380 | |||
381 | def __len__(self): | ||
382 | # see description for __repr__, len(obj) is the same. Unfortunately, __len__ is also | ||
383 | # used when determining whether an object is boolean or not, e.g. if proxied_object: | ||
384 | return self.__len__() | ||
385 | |||
386 | def proxied_type(self): | ||
387 | if type(self) is not ObjectProxy: | ||
388 | return type(self) | ||
389 | |||
390 | my_in = self.__local_dict['_in'] | ||
391 | my_out = self.__local_dict['_out'] | ||
392 | my_id = self.__local_dict['_id'] | ||
393 | request = Request('type', {'id':my_id}) | ||
394 | _write_request(request, my_out) | ||
395 | # dead list can be none because we know the result will always be | ||
396 | # a value and not an ObjectProxy itself | ||
397 | return _read_response(my_id, None, my_in, my_out, None) | ||
398 | |||
399 | class CallableProxy(object): | ||
400 | """\ | ||
401 | @class CallableProxy | ||
402 | @brief This class wraps a remote function in the Server | ||
403 | |||
404 | This class will be created by an Proxy during normal operation, | ||
405 | and users should not need to deal with this class directly.""" | ||
406 | |||
407 | def __init__(self, object_id, name, input, output, dead_list): | ||
408 | #_prnt("CallableProxy::__init__: %s, %s" % (object_id, name)) | ||
409 | self._object_id = object_id | ||
410 | self._name = name | ||
411 | self._in = input | ||
412 | self._out = output | ||
413 | self._dead_list = dead_list | ||
414 | |||
415 | def __call__(self, *args, **kwargs): | ||
416 | #_prnt("CallableProxy::__call__: %s, %s" % (args, kwargs)) | ||
417 | |||
418 | # Pass the call across. We never build a callable without | ||
419 | # having already checked if the method starts with '_' so we | ||
420 | # can safely pass this one to the remote object. | ||
421 | #_prnt("calling %s %s" % (self._object_id, self._name) | ||
422 | request = Request('call', {'id':self._object_id, 'name':self._name, 'args':args, 'kwargs':kwargs}) | ||
423 | _write_request(request, self._out) | ||
424 | return _read_response(self._object_id, self._name, self._in, self._out, self._dead_list) | ||
425 | |||
426 | class Server(object): | ||
427 | def __init__(self, input, output, export): | ||
428 | """\ | ||
429 | @param input a file-like object which supports read(). | ||
430 | @param output a file-like object which supports write() and flush(). | ||
431 | @param export an object, function, or map which is exported to clients | ||
432 | when the id is None.""" | ||
433 | #_log("Server::__init__") | ||
434 | self._in = input | ||
435 | self._out = output | ||
436 | self._export = export | ||
437 | self._next_id = 1 | ||
438 | self._objects = {} | ||
439 | |||
440 | def handle_status(self, object, req): | ||
441 | return { | ||
442 | 'object_count':len(self._objects), | ||
443 | 'next_id':self._next_id, | ||
444 | 'pid':os.getpid()} | ||
445 | |||
446 | def handle_getattr(self, object, req): | ||
447 | try: | ||
448 | return getattr(object, req['attribute']) | ||
449 | except AttributeError, e: | ||
450 | if hasattr(object, "__getitem__"): | ||
451 | return object[req['attribute']] | ||
452 | else: | ||
453 | raise e | ||
454 | #_log('getattr: %s' % str(response)) | ||
455 | |||
456 | def handle_setattr(self, object, req): | ||
457 | try: | ||
458 | return setattr(object, req['attribute'], req['value']) | ||
459 | except AttributeError, e: | ||
460 | if hasattr(object, "__setitem__"): | ||
461 | return object.__setitem__(req['attribute'], req['value']) | ||
462 | else: | ||
463 | raise e | ||
464 | |||
465 | def handle_getitem(self, object, req): | ||
466 | return object[req['key']] | ||
467 | |||
468 | def handle_setitem(self, object, req): | ||
469 | object[req['key']] = req['value'] | ||
470 | return None # *TODO figure out what the actual return value of __setitem__ should be | ||
471 | |||
472 | def handle_eq(self, object, req): | ||
473 | #_log("__eq__ %s %s" % (object, req)) | ||
474 | rhs = None | ||
475 | try: | ||
476 | rhs = self._objects[req['rhs']] | ||
477 | except KeyError, e: | ||
478 | return False | ||
479 | return (object == rhs) | ||
480 | |||
481 | def handle_call(self, object, req): | ||
482 | #_log("calling %s " % (req['name'])) | ||
483 | try: | ||
484 | fn = getattr(object, req['name']) | ||
485 | except AttributeError, e: | ||
486 | if hasattr(object, "__setitem__"): | ||
487 | fn = object[req['name']] | ||
488 | else: | ||
489 | raise e | ||
490 | |||
491 | return fn(*req['args'],**req['kwargs']) | ||
492 | |||
493 | def handle_del(self, object, req): | ||
494 | id = req['id'] | ||
495 | _log("del %s from %s" % (id, self._objects)) | ||
496 | |||
497 | # *TODO what does __del__ actually return? | ||
498 | del self._objects[id] | ||
499 | return None | ||
500 | |||
501 | def handle_type(self, object, req): | ||
502 | return type(object) | ||
503 | |||
504 | def loop(self): | ||
505 | """@brief Loop forever and respond to all requests.""" | ||
506 | _log("Server::loop") | ||
507 | while True: | ||
508 | try: | ||
509 | try: | ||
510 | str = _read_lp_hunk(self._in) | ||
511 | except EOFError: | ||
512 | sys.exit(0) # normal exit | ||
513 | request = cPickle.loads(str) | ||
514 | _log("request: %s (%s)" % (request, self._objects)) | ||
515 | req = request | ||
516 | id = None | ||
517 | object = None | ||
518 | try: | ||
519 | id = req['id'] | ||
520 | if id: | ||
521 | id = int(id) | ||
522 | object = self._objects[id] | ||
523 | #_log("id, object: %d %s" % (id, object)) | ||
524 | except Exception, e: | ||
525 | #_log("Exception %s" % str(e)) | ||
526 | pass | ||
527 | if object is None or id is None: | ||
528 | id = None | ||
529 | object = self._export | ||
530 | #_log("found object %s" % str(object)) | ||
531 | |||
532 | # Handle the request via a method with a special name on the server | ||
533 | handler_name = 'handle_%s' % request.action() | ||
534 | |||
535 | try: | ||
536 | handler = getattr(self, handler_name) | ||
537 | except AttributeError: | ||
538 | raise BadRequest, request.action() | ||
539 | |||
540 | response = handler(object, request) | ||
541 | |||
542 | # figure out what to do with the response, and respond | ||
543 | # apprpriately. | ||
544 | if request.action() in ['status', 'type']: | ||
545 | # have to handle these specially since we want to | ||
546 | # pickle up the actual value and not return a proxy | ||
547 | self.respond(['value', response]) | ||
548 | elif callable(response): | ||
549 | #_log("callable %s" % response) | ||
550 | self.respond(['callable']) | ||
551 | elif self.is_value(response): | ||
552 | self.respond(['value', response]) | ||
553 | else: | ||
554 | self._objects[self._next_id] = response | ||
555 | #_log("objects: %s" % str(self._objects)) | ||
556 | self.respond(['object', self._next_id]) | ||
557 | self._next_id += 1 | ||
558 | except SystemExit, e: | ||
559 | raise e | ||
560 | except Exception, e: | ||
561 | self.write_exception(e) | ||
562 | except: | ||
563 | self.write_exception(sys.exc_info()[0]) | ||
564 | |||
565 | def is_value(self, value): | ||
566 | """\ | ||
567 | @brief Test if value should be serialized as a simple dataset. | ||
568 | @param value The value to test. | ||
569 | @return Returns true if value is a simple serializeable set of data. | ||
570 | """ | ||
571 | return type(value) in (str,unicode,int,float,long,bool,type(None)) | ||
572 | |||
573 | def respond(self, body): | ||
574 | _log("responding with: %s" % body) | ||
575 | #_log("objects: %s" % self._objects) | ||
576 | s = cPickle.dumps(body) | ||
577 | _log(`s`) | ||
578 | str = _write_lp_hunk(self._out, s) | ||
579 | |||
580 | def write_exception(self, e): | ||
581 | """@brief Helper method to respond with an exception.""" | ||
582 | #_log("exception: %s" % sys.exc_info()[0]) | ||
583 | # TODO: serialize traceback using generalization of code from mulib.htmlexception | ||
584 | self.respond(['exception', e]) | ||
585 | global _g_debug_mode | ||
586 | if _g_debug_mode: | ||
587 | _log("traceback: %s" % traceback.format_tb(sys.exc_info()[2])) | ||
588 | |||
589 | |||
590 | # test function used for testing that final except clause | ||
591 | def raise_a_weird_error(): | ||
592 | raise "oh noes you can raise a string" | ||
593 | |||
594 | # test function used for testing return of unpicklable exceptions | ||
595 | def raise_an_unpicklable_error(): | ||
596 | class Unpicklable(Exception): | ||
597 | pass | ||
598 | raise Unpicklable() | ||
599 | |||
600 | # test function used for testing return of picklable exceptions | ||
601 | def raise_standard_error(): | ||
602 | raise FloatingPointError() | ||
603 | |||
604 | # test function to make sure print doesn't break the wrapper | ||
605 | def print_string(str): | ||
606 | print str | ||
607 | |||
608 | # test function to make sure printing on stdout doesn't break the | ||
609 | # wrapper | ||
610 | def err_string(str): | ||
611 | print >>sys.stderr, str | ||
612 | |||
613 | def main(): | ||
614 | import optparse | ||
615 | parser = optparse.OptionParser( | ||
616 | usage="usage: %prog [options]", | ||
617 | description="Simple saranwrap.Server wrapper") | ||
618 | parser.add_option( | ||
619 | '-c', '--child', default=False, action='store_true', | ||
620 | help='Wrap an object serialed via setattr.') | ||
621 | parser.add_option( | ||
622 | '-m', '--module', type='string', dest='module', default=None, | ||
623 | help='a module to load and export.') | ||
624 | parser.add_option( | ||
625 | '-l', '--logfile', type='string', dest='logfile', default=None, | ||
626 | help='file to log to.') | ||
627 | options, args = parser.parse_args() | ||
628 | global _g_logfile | ||
629 | if options.logfile: | ||
630 | _g_logfile = open(options.logfile, 'a') | ||
631 | if options.module: | ||
632 | export = api.named(options.module) | ||
633 | server = Server(sys.stdin, sys.stdout, export) | ||
634 | elif options.child: | ||
635 | server = Server(sys.stdin, sys.stdout, {}) | ||
636 | |||
637 | # *HACK: some modules may emit on stderr, which breaks everything. | ||
638 | class NullSTDOut(object): | ||
639 | def write(a, b): | ||
640 | pass | ||
641 | sys.stderr = NullSTDOut() | ||
642 | sys.stdout = NullSTDOut() | ||
643 | |||
644 | # Loop until EOF | ||
645 | server.loop() | ||
646 | if _g_logfile: | ||
647 | _g_logfile.close() | ||
648 | |||
649 | |||
650 | if __name__ == "__main__": | ||
651 | main() | ||
diff --git a/linden/indra/lib/python/indra/ipc/siesta.py b/linden/indra/lib/python/indra/ipc/siesta.py index 5fbea29..b206f18 100644 --- a/linden/indra/lib/python/indra/ipc/siesta.py +++ b/linden/indra/lib/python/indra/ipc/siesta.py | |||
@@ -24,9 +24,9 @@ except ImportError: | |||
24 | 24 | ||
25 | llsd_parsers = { | 25 | llsd_parsers = { |
26 | 'application/json': json_decode, | 26 | 'application/json': json_decode, |
27 | 'application/llsd+binary': llsd.parse_binary, | 27 | llsd.BINARY_MIME_TYPE: llsd.parse_binary, |
28 | 'application/llsd+notation': llsd.parse_notation, | 28 | 'application/llsd+notation': llsd.parse_notation, |
29 | 'application/llsd+xml': llsd.parse_xml, | 29 | llsd.XML_MIME_TYPE: llsd.parse_xml, |
30 | 'application/xml': llsd.parse_xml, | 30 | 'application/xml': llsd.parse_xml, |
31 | } | 31 | } |
32 | 32 | ||
diff --git a/linden/indra/lib/python/indra/util/fastest_elementtree.py b/linden/indra/lib/python/indra/util/fastest_elementtree.py index 64aed09..2470143 100644 --- a/linden/indra/lib/python/indra/util/fastest_elementtree.py +++ b/linden/indra/lib/python/indra/util/fastest_elementtree.py | |||
@@ -2,9 +2,9 @@ | |||
2 | @file fastest_elementtree.py | 2 | @file fastest_elementtree.py |
3 | @brief Concealing some gnarly import logic in here. This should export the interface of elementtree. | 3 | @brief Concealing some gnarly import logic in here. This should export the interface of elementtree. |
4 | 4 | ||
5 | $LicenseInfo:firstyear=2006&license=mit$ | 5 | $LicenseInfo:firstyear=2008&license=mit$ |
6 | 6 | ||
7 | Copyright (c) 2006-2008, Linden Research, Inc. | 7 | Copyright (c) 2008, Linden Research, Inc. |
8 | 8 | ||
9 | Permission is hereby granted, free of charge, to any person obtaining a copy | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy |
10 | of this software and associated documentation files (the "Software"), to deal | 10 | of this software and associated documentation files (the "Software"), to deal |
@@ -26,27 +26,40 @@ THE SOFTWARE. | |||
26 | $/LicenseInfo$ | 26 | $/LicenseInfo$ |
27 | """ | 27 | """ |
28 | 28 | ||
29 | # Using celementree might cause some unforeseen problems so here's a | 29 | # The parsing exception raised by the underlying library depends |
30 | # on the ElementTree implementation we're using, so we provide an | ||
31 | # alias here. | ||
32 | # | ||
33 | # Use ElementTreeError as the exception type for catching parsing | ||
34 | # errors. | ||
35 | |||
36 | |||
37 | # Using cElementTree might cause some unforeseen problems, so here's a | ||
30 | # convenient off switch. | 38 | # convenient off switch. |
31 | 39 | ||
32 | # *NOTE: turned off cause of problems. :-( *TODO: debug | 40 | use_celementree = True |
33 | use_celementree = False | ||
34 | 41 | ||
35 | try: | 42 | try: |
36 | if not use_celementree: | 43 | if not use_celementree: |
37 | raise ImportError() | 44 | raise ImportError() |
38 | from cElementTree import * ## This does not work under Windows | 45 | # Python 2.3 and 2.4. |
46 | from cElementTree import * | ||
47 | ElementTreeError = SyntaxError | ||
39 | except ImportError: | 48 | except ImportError: |
40 | try: | 49 | try: |
41 | if not use_celementree: | 50 | if not use_celementree: |
42 | raise ImportError() | 51 | raise ImportError() |
43 | ## This is the name of cElementTree under python 2.5 | 52 | # Python 2.5 and above. |
44 | from xml.etree.cElementTree import * | 53 | from xml.etree.cElementTree import * |
54 | ElementTreeError = SyntaxError | ||
45 | except ImportError: | 55 | except ImportError: |
56 | # Pure Python code. | ||
46 | try: | 57 | try: |
47 | ## This is the old name of elementtree, for use with 2.3 | 58 | # Python 2.3 and 2.4. |
48 | from elementtree.ElementTree import * | 59 | from elementtree.ElementTree import * |
49 | except ImportError: | 60 | except ImportError: |
50 | ## This is the name of elementtree under python 2.5 | 61 | # Python 2.5 and above. |
51 | from xml.etree.ElementTree import * | 62 | from xml.etree.ElementTree import * |
52 | 63 | ||
64 | # The pure Python ElementTree module uses Expat for parsing. | ||
65 | from xml.parsers.expat import ExpatError as ElementTreeError | ||
diff --git a/linden/indra/lib/python/indra/util/iterators.py b/linden/indra/lib/python/indra/util/iterators.py new file mode 100644 index 0000000..6a98c97 --- /dev/null +++ b/linden/indra/lib/python/indra/util/iterators.py | |||
@@ -0,0 +1,63 @@ | |||
1 | """\ | ||
2 | @file iterators.py | ||
3 | @brief Useful general-purpose iterators. | ||
4 | |||
5 | $LicenseInfo:firstyear=2008&license=mit$ | ||
6 | |||
7 | Copyright (c) 2008, Linden Research, Inc. | ||
8 | |||
9 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
10 | of this software and associated documentation files (the "Software"), to deal | ||
11 | in the Software without restriction, including without limitation the rights | ||
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
13 | copies of the Software, and to permit persons to whom the Software is | ||
14 | furnished to do so, subject to the following conditions: | ||
15 | |||
16 | The above copyright notice and this permission notice shall be included in | ||
17 | all copies or substantial portions of the Software. | ||
18 | |||
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
25 | THE SOFTWARE. | ||
26 | $/LicenseInfo$ | ||
27 | """ | ||
28 | |||
29 | from __future__ import nested_scopes | ||
30 | |||
31 | def iter_chunks(rows, aggregate_size=100): | ||
32 | """ | ||
33 | Given an iterable set of items (@p rows), produces lists of up to @p | ||
34 | aggregate_size items at a time, for example: | ||
35 | |||
36 | iter_chunks([1,2,3,4,5,6,7,8,9,10], 3) | ||
37 | |||
38 | Values for @p aggregate_size < 1 will raise ValueError. | ||
39 | |||
40 | Will return a generator that produces, in the following order: | ||
41 | - [1, 2, 3] | ||
42 | - [4, 5, 6] | ||
43 | - [7, 8, 9] | ||
44 | - [10] | ||
45 | """ | ||
46 | if aggregate_size < 1: | ||
47 | raise ValueError() | ||
48 | |||
49 | def iter_chunks_inner(): | ||
50 | row_iter = iter(rows) | ||
51 | done = False | ||
52 | agg = [] | ||
53 | while not done: | ||
54 | try: | ||
55 | row = row_iter.next() | ||
56 | agg.append(row) | ||
57 | except StopIteration: | ||
58 | done = True | ||
59 | if agg and (len(agg) >= aggregate_size or done): | ||
60 | yield agg | ||
61 | agg = [] | ||
62 | |||
63 | return iter_chunks_inner() | ||
diff --git a/linden/indra/lib/python/indra/util/iterators_test.py b/linden/indra/lib/python/indra/util/iterators_test.py new file mode 100755 index 0000000..7fd9e73 --- /dev/null +++ b/linden/indra/lib/python/indra/util/iterators_test.py | |||
@@ -0,0 +1,72 @@ | |||
1 | """\ | ||
2 | @file iterators_test.py | ||
3 | @brief Test cases for iterators module. | ||
4 | |||
5 | $LicenseInfo:firstyear=2008&license=mit$ | ||
6 | |||
7 | Copyright (c) 2008, Linden Research, Inc. | ||
8 | |||
9 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
10 | of this software and associated documentation files (the "Software"), to deal | ||
11 | in the Software without restriction, including without limitation the rights | ||
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
13 | copies of the Software, and to permit persons to whom the Software is | ||
14 | furnished to do so, subject to the following conditions: | ||
15 | |||
16 | The above copyright notice and this permission notice shall be included in | ||
17 | all copies or substantial portions of the Software. | ||
18 | |||
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
25 | THE SOFTWARE. | ||
26 | $/LicenseInfo$ | ||
27 | """ | ||
28 | |||
29 | import unittest | ||
30 | |||
31 | from indra.util.iterators import iter_chunks | ||
32 | |||
33 | class TestIterChunks(unittest.TestCase): | ||
34 | """Unittests for iter_chunks""" | ||
35 | def test_bad_agg_size(self): | ||
36 | rows = [1,2,3,4] | ||
37 | self.assertRaises(ValueError, iter_chunks, rows, 0) | ||
38 | self.assertRaises(ValueError, iter_chunks, rows, -1) | ||
39 | |||
40 | try: | ||
41 | for i in iter_chunks(rows, 0): | ||
42 | pass | ||
43 | except ValueError: | ||
44 | pass | ||
45 | else: | ||
46 | self.fail() | ||
47 | |||
48 | try: | ||
49 | result = list(iter_chunks(rows, 0)) | ||
50 | except ValueError: | ||
51 | pass | ||
52 | else: | ||
53 | self.fail() | ||
54 | def test_empty(self): | ||
55 | rows = [] | ||
56 | result = list(iter_chunks(rows)) | ||
57 | self.assertEqual(result, []) | ||
58 | def test_small(self): | ||
59 | rows = [[1]] | ||
60 | result = list(iter_chunks(rows, 2)) | ||
61 | self.assertEqual(result, [[[1]]]) | ||
62 | def test_size(self): | ||
63 | rows = [[1],[2]] | ||
64 | result = list(iter_chunks(rows, 2)) | ||
65 | self.assertEqual(result, [[[1],[2]]]) | ||
66 | def test_multi_agg(self): | ||
67 | rows = [[1],[2],[3],[4],[5]] | ||
68 | result = list(iter_chunks(rows, 2)) | ||
69 | self.assertEqual(result, [[[1],[2]],[[3],[4]],[[5]]]) | ||
70 | |||
71 | if __name__ == "__main__": | ||
72 | unittest.main() | ||
diff --git a/linden/indra/lib/python/indra/util/llmanifest.py b/linden/indra/lib/python/indra/util/llmanifest.py index 4675177..a00d242 100644 --- a/linden/indra/lib/python/indra/util/llmanifest.py +++ b/linden/indra/lib/python/indra/util/llmanifest.py | |||
@@ -584,7 +584,7 @@ class LLManifest(object): | |||
584 | 584 | ||
585 | def wildcard_regex(self, src_glob, dst_glob): | 585 | def wildcard_regex(self, src_glob, dst_glob): |
586 | src_re = re.escape(src_glob) | 586 | src_re = re.escape(src_glob) |
587 | src_re = src_re.replace('\*', '([-a-zA-Z0-9._ ]+)') | 587 | src_re = src_re.replace('\*', '([-a-zA-Z0-9._ ]*)') |
588 | dst_temp = dst_glob | 588 | dst_temp = dst_glob |
589 | i = 1 | 589 | i = 1 |
590 | while dst_temp.count("*") > 0: | 590 | while dst_temp.count("*") > 0: |
@@ -621,6 +621,7 @@ class LLManifest(object): | |||
621 | count = 0 | 621 | count = 0 |
622 | if self.wildcard_pattern.search(src): | 622 | if self.wildcard_pattern.search(src): |
623 | for s,d in self.expand_globs(src, dst): | 623 | for s,d in self.expand_globs(src, dst): |
624 | assert(s != d) | ||
624 | count += self.process_file(s, d) | 625 | count += self.process_file(s, d) |
625 | else: | 626 | else: |
626 | # if we're specifying a single path (not a glob), | 627 | # if we're specifying a single path (not a glob), |
diff --git a/linden/indra/lib/python/indra/util/llperformance.py b/linden/indra/lib/python/indra/util/llperformance.py new file mode 100755 index 0000000..7c52730 --- /dev/null +++ b/linden/indra/lib/python/indra/util/llperformance.py | |||
@@ -0,0 +1,158 @@ | |||
1 | #!/usr/bin/python | ||
2 | |||
3 | # ------------------------------------------------ | ||
4 | # Sim metrics utility functions. | ||
5 | |||
6 | import glob, os, time, sys, stat, exceptions | ||
7 | |||
8 | from indra.base import llsd | ||
9 | |||
10 | gBlockMap = {} #Map of performance metric data with function hierarchy information. | ||
11 | gCurrentStatPath = "" | ||
12 | |||
13 | gIsLoggingEnabled=False | ||
14 | |||
15 | class LLPerfStat: | ||
16 | def __init__(self,key): | ||
17 | self.mTotalTime = 0 | ||
18 | self.mNumRuns = 0 | ||
19 | self.mName=key | ||
20 | self.mTimeStamp = int(time.time()*1000) | ||
21 | self.mUTCTime = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) | ||
22 | |||
23 | def __str__(self): | ||
24 | return "%f" % self.mTotalTime | ||
25 | |||
26 | def start(self): | ||
27 | self.mStartTime = int(time.time() * 1000000) | ||
28 | self.mNumRuns += 1 | ||
29 | |||
30 | def stop(self): | ||
31 | execution_time = int(time.time() * 1000000) - self.mStartTime | ||
32 | self.mTotalTime += execution_time | ||
33 | |||
34 | def get_map(self): | ||
35 | results={} | ||
36 | results['name']=self.mName | ||
37 | results['utc_time']=self.mUTCTime | ||
38 | results['timestamp']=self.mTimeStamp | ||
39 | results['us']=self.mTotalTime | ||
40 | results['count']=self.mNumRuns | ||
41 | return results | ||
42 | |||
43 | class PerfError(exceptions.Exception): | ||
44 | def __init__(self): | ||
45 | return | ||
46 | |||
47 | def __Str__(self): | ||
48 | print "","Unfinished LLPerfBlock" | ||
49 | |||
50 | class LLPerfBlock: | ||
51 | def __init__( self, key ): | ||
52 | global gBlockMap | ||
53 | global gCurrentStatPath | ||
54 | global gIsLoggingEnabled | ||
55 | |||
56 | #Check to see if we're running metrics right now. | ||
57 | if gIsLoggingEnabled: | ||
58 | self.mRunning = True #Mark myself as running. | ||
59 | |||
60 | self.mPreviousStatPath = gCurrentStatPath | ||
61 | gCurrentStatPath += "/" + key | ||
62 | if gCurrentStatPath not in gBlockMap: | ||
63 | gBlockMap[gCurrentStatPath] = LLPerfStat(key) | ||
64 | |||
65 | self.mStat = gBlockMap[gCurrentStatPath] | ||
66 | self.mStat.start() | ||
67 | |||
68 | def finish( self ): | ||
69 | global gBlockMap | ||
70 | global gIsLoggingEnabled | ||
71 | |||
72 | if gIsLoggingEnabled: | ||
73 | self.mStat.stop() | ||
74 | self.mRunning = False | ||
75 | gCurrentStatPath = self.mPreviousStatPath | ||
76 | |||
77 | # def __del__( self ): | ||
78 | # if self.mRunning: | ||
79 | # #SPATTERS FIXME | ||
80 | # raise PerfError | ||
81 | |||
82 | class LLPerformance: | ||
83 | #-------------------------------------------------- | ||
84 | # Determine whether or not we want to log statistics | ||
85 | |||
86 | def __init__( self, process_name = "python" ): | ||
87 | self.process_name = process_name | ||
88 | self.init_testing() | ||
89 | self.mTimeStamp = int(time.time()*1000) | ||
90 | self.mUTCTime = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) | ||
91 | |||
92 | def init_testing( self ): | ||
93 | global gIsLoggingEnabled | ||
94 | |||
95 | host_performance_file = "/dev/shm/simperf/simperf_proc_config.llsd" | ||
96 | |||
97 | #If file exists, open | ||
98 | if os.path.exists(host_performance_file): | ||
99 | file = open (host_performance_file,'r') | ||
100 | |||
101 | #Read serialized LLSD from file. | ||
102 | body = llsd.parse(file.read()) | ||
103 | |||
104 | #Calculate time since file last modified. | ||
105 | stats = os.stat(host_performance_file) | ||
106 | now = time.time() | ||
107 | mod = stats[stat.ST_MTIME] | ||
108 | age = now - mod | ||
109 | |||
110 | if age < ( body['duration'] ): | ||
111 | gIsLoggingEnabled = True | ||
112 | |||
113 | |||
114 | def get ( self ): | ||
115 | global gIsLoggingEnabled | ||
116 | return gIsLoggingEnabled | ||
117 | |||
118 | #def output(self,ptr,path): | ||
119 | # if 'stats' in ptr: | ||
120 | # stats = ptr['stats'] | ||
121 | # self.mOutputPtr[path] = stats.get_map() | ||
122 | |||
123 | # if 'children' in ptr: | ||
124 | # children=ptr['children'] | ||
125 | |||
126 | # curptr = self.mOutputPtr | ||
127 | # curchildren={} | ||
128 | # curptr['children'] = curchildren | ||
129 | |||
130 | # for key in children: | ||
131 | # curchildren[key]={} | ||
132 | # self.mOutputPtr = curchildren[key] | ||
133 | # self.output(children[key],path + '/' + key) | ||
134 | |||
135 | def done(self): | ||
136 | global gBlockMap | ||
137 | |||
138 | if not self.get(): | ||
139 | return | ||
140 | |||
141 | output_name = "/dev/shm/simperf/%s_proc.%d.llsd" % (self.process_name, os.getpid()) | ||
142 | output_file = open(output_name, 'w') | ||
143 | process_info = { | ||
144 | "name" : self.process_name, | ||
145 | "pid" : os.getpid(), | ||
146 | "ppid" : os.getppid(), | ||
147 | "timestamp" : self.mTimeStamp, | ||
148 | "utc_time" : self.mUTCTime, | ||
149 | } | ||
150 | output_file.write(llsd.format_notation(process_info)) | ||
151 | output_file.write('\n') | ||
152 | |||
153 | for key in gBlockMap.keys(): | ||
154 | gBlockMap[key] = gBlockMap[key].get_map() | ||
155 | output_file.write(llsd.format_notation(gBlockMap)) | ||
156 | output_file.write('\n') | ||
157 | output_file.close() | ||
158 | |||
diff --git a/linden/indra/lib/python/indra/util/named_query.py b/linden/indra/lib/python/indra/util/named_query.py index 20f2ec7..c5fb498 100644 --- a/linden/indra/lib/python/indra/util/named_query.py +++ b/linden/indra/lib/python/indra/util/named_query.py | |||
@@ -63,7 +63,7 @@ def _init_g_named_manager(sql_dir = None): | |||
63 | 63 | ||
64 | # extra fallback directory in case config doesn't return what we want | 64 | # extra fallback directory in case config doesn't return what we want |
65 | if sql_dir is None: | 65 | if sql_dir is None: |
66 | sql_dir = os.path.dirname(__file__) + "../../../../web/dataservice/sql" | 66 | sql_dir = os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "web", "dataservice", "sql") |
67 | 67 | ||
68 | global _g_named_manager | 68 | global _g_named_manager |
69 | _g_named_manager = NamedQueryManager( | 69 | _g_named_manager = NamedQueryManager( |
@@ -103,11 +103,12 @@ class NamedQuery(object): | |||
103 | def __init__(self, name, filename): | 103 | def __init__(self, name, filename): |
104 | """ Construct a NamedQuery object. The name argument is an | 104 | """ Construct a NamedQuery object. The name argument is an |
105 | arbitrary name as a handle for the query, and the filename is | 105 | arbitrary name as a handle for the query, and the filename is |
106 | a path to a file containing an llsd named query document.""" | 106 | a path to a file or a file-like object containing an llsd named |
107 | query document.""" | ||
107 | self._stat_interval_seconds = 5 # 5 seconds | 108 | self._stat_interval_seconds = 5 # 5 seconds |
108 | self._name = name | 109 | self._name = name |
109 | if (filename is not None) \ | 110 | if (filename is not None and isinstance(filename, (str, unicode)) |
110 | and (NQ_FILE_SUFFIX != filename[-NQ_FILE_SUFFIX_LEN:]): | 111 | and NQ_FILE_SUFFIX != filename[-NQ_FILE_SUFFIX_LEN:]): |
111 | filename = filename + NQ_FILE_SUFFIX | 112 | filename = filename + NQ_FILE_SUFFIX |
112 | self._location = filename | 113 | self._location = filename |
113 | self._alternative = dict() | 114 | self._alternative = dict() |
@@ -122,8 +123,8 @@ class NamedQuery(object): | |||
122 | 123 | ||
123 | def get_modtime(self): | 124 | def get_modtime(self): |
124 | """ Returns the mtime (last modified time) of the named query | 125 | """ Returns the mtime (last modified time) of the named query |
125 | file, if such exists.""" | 126 | filename. For file-like objects, expect a modtime of 0""" |
126 | if self._location: | 127 | if self._location and isinstance(self._location, (str, unicode)): |
127 | return os.path.getmtime(self._location) | 128 | return os.path.getmtime(self._location) |
128 | return 0 | 129 | return 0 |
129 | 130 | ||
@@ -131,7 +132,12 @@ class NamedQuery(object): | |||
131 | """ Loads and parses the named query file into self. Does | 132 | """ Loads and parses the named query file into self. Does |
132 | nothing if self.location is nonexistant.""" | 133 | nothing if self.location is nonexistant.""" |
133 | if self._location: | 134 | if self._location: |
134 | self._reference_contents(llsd.parse(open(self._location).read())) | 135 | if isinstance(self._location, (str, unicode)): |
136 | contents = llsd.parse(open(self._location).read()) | ||
137 | else: | ||
138 | # we probably have a file-like object. Godspeed! | ||
139 | contents = llsd.parse(self._location.read()) | ||
140 | self._reference_contents(contents) | ||
135 | # Check for alternative implementations | 141 | # Check for alternative implementations |
136 | try: | 142 | try: |
137 | for name, alt in self._contents['alternative'].items(): | 143 | for name, alt in self._contents['alternative'].items(): |
diff --git a/linden/indra/lib/python/indra/util/simperf_host_xml_parser.py b/linden/indra/lib/python/indra/util/simperf_host_xml_parser.py new file mode 100755 index 0000000..b608415 --- /dev/null +++ b/linden/indra/lib/python/indra/util/simperf_host_xml_parser.py | |||
@@ -0,0 +1,338 @@ | |||
1 | #!/usr/bin/env python | ||
2 | """\ | ||
3 | @file simperf_host_xml_parser.py | ||
4 | @brief Digest collector's XML dump and convert to simple dict/list structure | ||
5 | |||
6 | $LicenseInfo:firstyear=2008&license=mit$ | ||
7 | |||
8 | Copyright (c) 2008, Linden Research, Inc. | ||
9 | |||
10 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
11 | of this software and associated documentation files (the "Software"), to deal | ||
12 | in the Software without restriction, including without limitation the rights | ||
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
14 | copies of the Software, and to permit persons to whom the Software is | ||
15 | furnished to do so, subject to the following conditions: | ||
16 | |||
17 | The above copyright notice and this permission notice shall be included in | ||
18 | all copies or substantial portions of the Software. | ||
19 | |||
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
26 | THE SOFTWARE. | ||
27 | $/LicenseInfo$ | ||
28 | """ | ||
29 | |||
30 | import sys, os, getopt, time | ||
31 | import simplejson | ||
32 | from xml import sax | ||
33 | |||
34 | |||
35 | def usage(): | ||
36 | print "Usage:" | ||
37 | print sys.argv[0] + " [options]" | ||
38 | print " Convert RRD's XML dump to JSON. Script to convert the simperf_host_collector-" | ||
39 | print " generated RRD dump into JSON. Steps include converting selected named" | ||
40 | print " fields from GAUGE type to COUNTER type by computing delta with preceding" | ||
41 | print " values. Top-level named fields are:" | ||
42 | |||
43 | print " lastupdate Time (javascript timestamp) of last data sample" | ||
44 | print " step Time in seconds between samples" | ||
45 | print " ds Data specification (name/type) for each column" | ||
46 | print " database Table of data samples, one time step per row" | ||
47 | |||
48 | print "Options:" | ||
49 | print " -i, --in Input settings filename. (Default: stdin)" | ||
50 | print " -o, --out Output settings filename. (Default: stdout)" | ||
51 | print " -h, --help Print this message and exit." | ||
52 | |||
53 | print "Example: %s -i rrddump.xml -o rrddump.json" % sys.argv[0] | ||
54 | |||
55 | print "Interfaces:" | ||
56 | print " class SimPerfHostXMLParser() # SAX content handler" | ||
57 | print " def simperf_host_xml_fixup(parser) # post-parse value fixup" | ||
58 | |||
59 | class SimPerfHostXMLParser(sax.handler.ContentHandler): | ||
60 | |||
61 | def __init__(self): | ||
62 | pass | ||
63 | |||
64 | def startDocument(self): | ||
65 | self.rrd_last_update = 0 # public | ||
66 | self.rrd_step = 0 # public | ||
67 | self.rrd_ds = [] # public | ||
68 | self.rrd_records = [] # public | ||
69 | self._rrd_level = 0 | ||
70 | self._rrd_parse_state = 0 | ||
71 | self._rrd_chars = "" | ||
72 | self._rrd_capture = False | ||
73 | self._rrd_ds_val = {} | ||
74 | self._rrd_data_row = [] | ||
75 | self._rrd_data_row_has_nan = False | ||
76 | |||
77 | def endDocument(self): | ||
78 | pass | ||
79 | |||
80 | # Nasty little ad-hoc state machine to extract the elements that are | ||
81 | # necessary from the 'rrdtool dump' XML output. The same element | ||
82 | # name '<ds>' is used for two different data sets so we need to pay | ||
83 | # some attention to the actual structure to get the ones we want | ||
84 | # and ignore the ones we don't. | ||
85 | |||
86 | def startElement(self, name, attrs): | ||
87 | self._rrd_level = self._rrd_level + 1 | ||
88 | self._rrd_capture = False | ||
89 | if self._rrd_level == 1: | ||
90 | if name == "rrd" and self._rrd_parse_state == 0: | ||
91 | self._rrd_parse_state = 1 # In <rrd> | ||
92 | self._rrd_capture = True | ||
93 | self._rrd_chars = "" | ||
94 | elif self._rrd_level == 2: | ||
95 | if self._rrd_parse_state == 1: | ||
96 | if name == "lastupdate": | ||
97 | self._rrd_parse_state = 2 # In <rrd><lastupdate> | ||
98 | self._rrd_capture = True | ||
99 | self._rrd_chars = "" | ||
100 | elif name == "step": | ||
101 | self._rrd_parse_state = 3 # In <rrd><step> | ||
102 | self._rrd_capture = True | ||
103 | self._rrd_chars = "" | ||
104 | elif name == "ds": | ||
105 | self._rrd_parse_state = 4 # In <rrd><ds> | ||
106 | self._rrd_ds_val = {} | ||
107 | self._rrd_chars = "" | ||
108 | elif name == "rra": | ||
109 | self._rrd_parse_state = 5 # In <rrd><rra> | ||
110 | elif self._rrd_level == 3: | ||
111 | if self._rrd_parse_state == 4: | ||
112 | if name == "name": | ||
113 | self._rrd_parse_state = 6 # In <rrd><ds><name> | ||
114 | self._rrd_capture = True | ||
115 | self._rrd_chars = "" | ||
116 | elif name == "type": | ||
117 | self._rrd_parse_state = 7 # In <rrd><ds><type> | ||
118 | self._rrd_capture = True | ||
119 | self._rrd_chars = "" | ||
120 | elif self._rrd_parse_state == 5: | ||
121 | if name == "database": | ||
122 | self._rrd_parse_state = 8 # In <rrd><rra><database> | ||
123 | elif self._rrd_level == 4: | ||
124 | if self._rrd_parse_state == 8: | ||
125 | if name == "row": | ||
126 | self._rrd_parse_state = 9 # In <rrd><rra><database><row> | ||
127 | self._rrd_data_row = [] | ||
128 | self._rrd_data_row_has_nan = False | ||
129 | elif self._rrd_level == 5: | ||
130 | if self._rrd_parse_state == 9: | ||
131 | if name == "v": | ||
132 | self._rrd_parse_state = 10 # In <rrd><rra><database><row><v> | ||
133 | self._rrd_capture = True | ||
134 | self._rrd_chars = "" | ||
135 | |||
136 | def endElement(self, name): | ||
137 | self._rrd_capture = False | ||
138 | if self._rrd_parse_state == 10: | ||
139 | self._rrd_capture = self._rrd_level == 6 | ||
140 | if self._rrd_level == 5: | ||
141 | if self._rrd_chars == "NaN": | ||
142 | self._rrd_data_row_has_nan = True | ||
143 | else: | ||
144 | self._rrd_data_row.append(self._rrd_chars) | ||
145 | self._rrd_parse_state = 9 # In <rrd><rra><database><row> | ||
146 | elif self._rrd_parse_state == 9: | ||
147 | if self._rrd_level == 4: | ||
148 | if not self._rrd_data_row_has_nan: | ||
149 | self.rrd_records.append(self._rrd_data_row) | ||
150 | self._rrd_parse_state = 8 # In <rrd><rra><database> | ||
151 | elif self._rrd_parse_state == 8: | ||
152 | if self._rrd_level == 3: | ||
153 | self._rrd_parse_state = 5 # In <rrd><rra> | ||
154 | elif self._rrd_parse_state == 7: | ||
155 | if self._rrd_level == 3: | ||
156 | self._rrd_ds_val["type"] = self._rrd_chars | ||
157 | self._rrd_parse_state = 4 # In <rrd><ds> | ||
158 | elif self._rrd_parse_state == 6: | ||
159 | if self._rrd_level == 3: | ||
160 | self._rrd_ds_val["name"] = self._rrd_chars | ||
161 | self._rrd_parse_state = 4 # In <rrd><ds> | ||
162 | elif self._rrd_parse_state == 5: | ||
163 | if self._rrd_level == 2: | ||
164 | self._rrd_parse_state = 1 # In <rrd> | ||
165 | elif self._rrd_parse_state == 4: | ||
166 | if self._rrd_level == 2: | ||
167 | self.rrd_ds.append(self._rrd_ds_val) | ||
168 | self._rrd_parse_state = 1 # In <rrd> | ||
169 | elif self._rrd_parse_state == 3: | ||
170 | if self._rrd_level == 2: | ||
171 | self.rrd_step = long(self._rrd_chars) | ||
172 | self._rrd_parse_state = 1 # In <rrd> | ||
173 | elif self._rrd_parse_state == 2: | ||
174 | if self._rrd_level == 2: | ||
175 | self.rrd_last_update = long(self._rrd_chars) | ||
176 | self._rrd_parse_state = 1 # In <rrd> | ||
177 | elif self._rrd_parse_state == 1: | ||
178 | if self._rrd_level == 1: | ||
179 | self._rrd_parse_state = 0 # At top | ||
180 | |||
181 | if self._rrd_level: | ||
182 | self._rrd_level = self._rrd_level - 1 | ||
183 | |||
184 | def characters(self, content): | ||
185 | if self._rrd_capture: | ||
186 | self._rrd_chars = self._rrd_chars + content.strip() | ||
187 | |||
188 | def _make_numeric(value): | ||
189 | try: | ||
190 | value = float(value) | ||
191 | except: | ||
192 | value = "" | ||
193 | return value | ||
194 | |||
195 | def simperf_host_xml_fixup(parser, filter_start_time = None, filter_end_time = None): | ||
196 | # Fixup for GAUGE fields that are really COUNTS. They | ||
197 | # were forced to GAUGE to try to disable rrdtool's | ||
198 | # data interpolation/extrapolation for non-uniform time | ||
199 | # samples. | ||
200 | fixup_tags = [ "cpu_user", | ||
201 | "cpu_nice", | ||
202 | "cpu_sys", | ||
203 | "cpu_idle", | ||
204 | "cpu_waitio", | ||
205 | "cpu_intr", | ||
206 | # "file_active", | ||
207 | # "file_free", | ||
208 | # "inode_active", | ||
209 | # "inode_free", | ||
210 | "netif_in_kb", | ||
211 | "netif_in_pkts", | ||
212 | "netif_in_errs", | ||
213 | "netif_in_drop", | ||
214 | "netif_out_kb", | ||
215 | "netif_out_pkts", | ||
216 | "netif_out_errs", | ||
217 | "netif_out_drop", | ||
218 | "vm_page_in", | ||
219 | "vm_page_out", | ||
220 | "vm_swap_in", | ||
221 | "vm_swap_out", | ||
222 | #"vm_mem_total", | ||
223 | #"vm_mem_used", | ||
224 | #"vm_mem_active", | ||
225 | #"vm_mem_inactive", | ||
226 | #"vm_mem_free", | ||
227 | #"vm_mem_buffer", | ||
228 | #"vm_swap_cache", | ||
229 | #"vm_swap_total", | ||
230 | #"vm_swap_used", | ||
231 | #"vm_swap_free", | ||
232 | "cpu_interrupts", | ||
233 | "cpu_switches", | ||
234 | "cpu_forks" ] | ||
235 | |||
236 | col_count = len(parser.rrd_ds) | ||
237 | row_count = len(parser.rrd_records) | ||
238 | |||
239 | # Process the last row separately, just to make all values numeric. | ||
240 | for j in range(col_count): | ||
241 | parser.rrd_records[row_count - 1][j] = _make_numeric(parser.rrd_records[row_count - 1][j]) | ||
242 | |||
243 | # Process all other row/columns. | ||
244 | last_different_row = row_count - 1 | ||
245 | current_row = row_count - 2 | ||
246 | while current_row >= 0: | ||
247 | # Check for a different value than the previous row. If everything is the same | ||
248 | # then this is probably just a filler/bogus entry. | ||
249 | is_different = False | ||
250 | for j in range(col_count): | ||
251 | parser.rrd_records[current_row][j] = _make_numeric(parser.rrd_records[current_row][j]) | ||
252 | if parser.rrd_records[current_row][j] != parser.rrd_records[last_different_row][j]: | ||
253 | # We're good. This is a different row. | ||
254 | is_different = True | ||
255 | |||
256 | if not is_different: | ||
257 | # This is a filler/bogus entry. Just ignore it. | ||
258 | for j in range(col_count): | ||
259 | parser.rrd_records[current_row][j] = float('nan') | ||
260 | else: | ||
261 | # Some tags need to be converted into deltas. | ||
262 | for j in range(col_count): | ||
263 | if parser.rrd_ds[j]["name"] in fixup_tags: | ||
264 | parser.rrd_records[last_different_row][j] = \ | ||
265 | parser.rrd_records[last_different_row][j] - parser.rrd_records[current_row][j] | ||
266 | last_different_row = current_row | ||
267 | |||
268 | current_row -= 1 | ||
269 | |||
270 | # Set fixup_tags in the first row to 'nan' since they aren't useful anymore. | ||
271 | for j in range(col_count): | ||
272 | if parser.rrd_ds[j]["name"] in fixup_tags: | ||
273 | parser.rrd_records[0][j] = float('nan') | ||
274 | |||
275 | # Add a timestamp to each row and to the catalog. Format and name | ||
276 | # chosen to match other simulator logging (hopefully). | ||
277 | start_time = parser.rrd_last_update - (parser.rrd_step * (row_count - 1)) | ||
278 | # Build a filtered list of rrd_records if we are limited to a time range. | ||
279 | filter_records = False | ||
280 | if filter_start_time is not None or filter_end_time is not None: | ||
281 | filter_records = True | ||
282 | filtered_rrd_records = [] | ||
283 | if filter_start_time is None: | ||
284 | filter_start_time = start_time * 1000 | ||
285 | if filter_end_time is None: | ||
286 | filter_end_time = parser.rrd_last_update * 1000 | ||
287 | |||
288 | for i in range(row_count): | ||
289 | record_timestamp = (start_time + (i * parser.rrd_step)) * 1000 | ||
290 | parser.rrd_records[i].insert(0, record_timestamp) | ||
291 | if filter_records: | ||
292 | if filter_start_time <= record_timestamp and record_timestamp <= filter_end_time: | ||
293 | filtered_rrd_records.append(parser.rrd_records[i]) | ||
294 | |||
295 | if filter_records: | ||
296 | parser.rrd_records = filtered_rrd_records | ||
297 | |||
298 | parser.rrd_ds.insert(0, {"type": "GAUGE", "name": "javascript_timestamp"}) | ||
299 | |||
300 | |||
301 | def main(argv=None): | ||
302 | opts, args = getopt.getopt(sys.argv[1:], "i:o:h", ["in=", "out=", "help"]) | ||
303 | input_file = sys.stdin | ||
304 | output_file = sys.stdout | ||
305 | for o, a in opts: | ||
306 | if o in ("-i", "--in"): | ||
307 | input_file = open(a, 'r') | ||
308 | if o in ("-o", "--out"): | ||
309 | output_file = open(a, 'w') | ||
310 | if o in ("-h", "--help"): | ||
311 | usage() | ||
312 | sys.exit(0) | ||
313 | |||
314 | # Using the SAX parser as it is at least 4X faster and far, far | ||
315 | # smaller on this dataset than the DOM-based interface in xml.dom.minidom. | ||
316 | # With SAX and a 5.4MB xml file, this requires about seven seconds of | ||
317 | # wall-clock time and 32MB VSZ. With the DOM interface, about 22 seconds | ||
318 | # and over 270MB VSZ. | ||
319 | |||
320 | handler = SimPerfHostXMLParser() | ||
321 | sax.parse(input_file, handler) | ||
322 | if input_file != sys.stdin: | ||
323 | input_file.close() | ||
324 | |||
325 | # Various format fixups: string-to-num, gauge-to-counts, add | ||
326 | # a time stamp, etc. | ||
327 | simperf_host_xml_fixup(handler) | ||
328 | |||
329 | # Create JSONable dict with interesting data and format/print it | ||
330 | print >>output_file, simplejson.dumps({ "step" : handler.rrd_step, | ||
331 | "lastupdate": handler.rrd_last_update * 1000, | ||
332 | "ds" : handler.rrd_ds, | ||
333 | "database" : handler.rrd_records }) | ||
334 | |||
335 | return 0 | ||
336 | |||
337 | if __name__ == "__main__": | ||
338 | sys.exit(main()) | ||
diff --git a/linden/indra/lib/python/indra/util/simperf_oprof_interface.py b/linden/indra/lib/python/indra/util/simperf_oprof_interface.py new file mode 100755 index 0000000..a7e9a4c --- /dev/null +++ b/linden/indra/lib/python/indra/util/simperf_oprof_interface.py | |||
@@ -0,0 +1,160 @@ | |||
1 | #!/usr/bin/env python | ||
2 | """\ | ||
3 | @file simperf_oprof_interface.py | ||
4 | @brief Manage OProfile data collection on a host | ||
5 | |||
6 | $LicenseInfo:firstyear=2008&license=internal$ | ||
7 | |||
8 | Copyright (c) 2008, Linden Research, Inc. | ||
9 | |||
10 | The following source code is PROPRIETARY AND CONFIDENTIAL. Use of | ||
11 | this source code is governed by the Linden Lab Source Code Disclosure | ||
12 | Agreement ("Agreement") previously entered between you and Linden | ||
13 | Lab. By accessing, using, copying, modifying or distributing this | ||
14 | software, you acknowledge that you have been informed of your | ||
15 | obligations under the Agreement and agree to abide by those obligations. | ||
16 | |||
17 | ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
18 | WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
19 | COMPLETENESS OR PERFORMANCE. | ||
20 | $/LicenseInfo$ | ||
21 | """ | ||
22 | |||
23 | import sys, os, getopt | ||
24 | import simplejson | ||
25 | |||
26 | |||
27 | def usage(): | ||
28 | print "Usage:" | ||
29 | print sys.argv[0] + " [options]" | ||
30 | print " Digest the OProfile report forms that come out of the" | ||
31 | print " simperf_oprof_ctl program's -r/--report command. The result" | ||
32 | print " is an array of dictionaires with the following keys:" | ||
33 | |||
34 | print " symbol Name of sampled, calling, or called procedure" | ||
35 | print " file Executable or library where symbol resides" | ||
36 | print " percentage Percentage contribution to profile, calls or called" | ||
37 | print " samples Sample count" | ||
38 | print " calls Methods called by the method in question (full only)" | ||
39 | print " called_by Methods calling the method (full only)" | ||
40 | |||
41 | print " For 'full' reports the two keys 'calls' and 'called_by' are" | ||
42 | print " themselves arrays of dictionaries based on the first four keys." | ||
43 | |||
44 | print "Return Codes:" | ||
45 | print " None. Aggressively digests everything. Will likely mung results" | ||
46 | print " if a program or library has whitespace in its name." | ||
47 | |||
48 | print "Options:" | ||
49 | print " -i, --in Input settings filename. (Default: stdin)" | ||
50 | print " -o, --out Output settings filename. (Default: stdout)" | ||
51 | print " -h, --help Print this message and exit." | ||
52 | |||
53 | print "Interfaces:" | ||
54 | print " class SimPerfOProfileInterface()" | ||
55 | |||
56 | class SimPerfOProfileInterface: | ||
57 | def __init__(self): | ||
58 | self.isBrief = True # public | ||
59 | self.isValid = False # public | ||
60 | self.result = [] # public | ||
61 | |||
62 | def parse(self, input): | ||
63 | in_samples = False | ||
64 | for line in input: | ||
65 | if in_samples: | ||
66 | if line[0:6] == "------": | ||
67 | self.isBrief = False | ||
68 | self._parseFull(input) | ||
69 | else: | ||
70 | self._parseBrief(input, line) | ||
71 | self.isValid = True | ||
72 | return | ||
73 | try: | ||
74 | hd1, remain = line.split(None, 1) | ||
75 | if hd1 == "samples": | ||
76 | in_samples = True | ||
77 | except ValueError: | ||
78 | pass | ||
79 | |||
80 | def _parseBrief(self, input, line1): | ||
81 | try: | ||
82 | fld1, fld2, fld3, fld4 = line1.split(None, 3) | ||
83 | self.result.append({"samples" : fld1, | ||
84 | "percentage" : fld2, | ||
85 | "file" : fld3, | ||
86 | "symbol" : fld4.strip("\n")}) | ||
87 | except ValueError: | ||
88 | pass | ||
89 | for line in input: | ||
90 | try: | ||
91 | fld1, fld2, fld3, fld4 = line.split(None, 3) | ||
92 | self.result.append({"samples" : fld1, | ||
93 | "percentage" : fld2, | ||
94 | "file" : fld3, | ||
95 | "symbol" : fld4.strip("\n")}) | ||
96 | except ValueError: | ||
97 | pass | ||
98 | |||
99 | def _parseFull(self, input): | ||
100 | state = 0 # In 'called_by' section | ||
101 | calls = [] | ||
102 | called_by = [] | ||
103 | current = {} | ||
104 | for line in input: | ||
105 | if line[0:6] == "------": | ||
106 | if len(current): | ||
107 | current["calls"] = calls | ||
108 | current["called_by"] = called_by | ||
109 | self.result.append(current) | ||
110 | state = 0 | ||
111 | calls = [] | ||
112 | called_by = [] | ||
113 | current = {} | ||
114 | else: | ||
115 | try: | ||
116 | fld1, fld2, fld3, fld4 = line.split(None, 3) | ||
117 | tmp = {"samples" : fld1, | ||
118 | "percentage" : fld2, | ||
119 | "file" : fld3, | ||
120 | "symbol" : fld4.strip("\n")} | ||
121 | except ValueError: | ||
122 | continue | ||
123 | if line[0] != " ": | ||
124 | current = tmp | ||
125 | state = 1 # In 'calls' section | ||
126 | elif state == 0: | ||
127 | called_by.append(tmp) | ||
128 | else: | ||
129 | calls.append(tmp) | ||
130 | if len(current): | ||
131 | current["calls"] = calls | ||
132 | current["called_by"] = called_by | ||
133 | self.result.append(current) | ||
134 | |||
135 | |||
136 | def main(argv=None): | ||
137 | opts, args = getopt.getopt(sys.argv[1:], "i:o:h", ["in=", "out=", "help"]) | ||
138 | input_file = sys.stdin | ||
139 | output_file = sys.stdout | ||
140 | for o, a in opts: | ||
141 | if o in ("-i", "--in"): | ||
142 | input_file = open(a, 'r') | ||
143 | if o in ("-o", "--out"): | ||
144 | output_file = open(a, 'w') | ||
145 | if o in ("-h", "--help"): | ||
146 | usage() | ||
147 | sys.exit(0) | ||
148 | |||
149 | oprof = SimPerfOProfileInterface() | ||
150 | oprof.parse(input_file) | ||
151 | if input_file != sys.stdin: | ||
152 | input_file.close() | ||
153 | |||
154 | # Create JSONable dict with interesting data and format/print it | ||
155 | print >>output_file, simplejson.dumps(oprof.result) | ||
156 | |||
157 | return 0 | ||
158 | |||
159 | if __name__ == "__main__": | ||
160 | sys.exit(main()) | ||
diff --git a/linden/indra/lib/python/indra/util/simperf_proc_interface.py b/linden/indra/lib/python/indra/util/simperf_proc_interface.py new file mode 100755 index 0000000..62a63fa --- /dev/null +++ b/linden/indra/lib/python/indra/util/simperf_proc_interface.py | |||
@@ -0,0 +1,164 @@ | |||
1 | #!/usr/bin/python | ||
2 | |||
3 | # ---------------------------------------------------- | ||
4 | # Utility to extract log messages from *.<pid>.llsd | ||
5 | # files that contain performance statistics. | ||
6 | |||
7 | # ---------------------------------------------------- | ||
8 | import sys, os | ||
9 | |||
10 | if os.path.exists("setup-path.py"): | ||
11 | execfile("setup-path.py") | ||
12 | |||
13 | from indra.base import llsd | ||
14 | |||
15 | DEFAULT_PATH="/dev/shm/simperf/" | ||
16 | |||
17 | |||
18 | # ---------------------------------------------------- | ||
19 | # Pull out the stats and return a single document | ||
20 | def parse_logfile(filename, target_column=None, verbose=False): | ||
21 | full_doc = [] | ||
22 | # Open source temp log file. Let exceptions percolate up. | ||
23 | sourcefile = open( filename,'r') | ||
24 | |||
25 | if verbose: | ||
26 | print "Reading " + filename | ||
27 | |||
28 | # Parse and output all lines from the temp file | ||
29 | for line in sourcefile.xreadlines(): | ||
30 | partial_doc = llsd.parse(line) | ||
31 | if partial_doc is not None: | ||
32 | if target_column is None: | ||
33 | full_doc.append(partial_doc) | ||
34 | else: | ||
35 | trim_doc = { target_column: partial_doc[target_column] } | ||
36 | if target_column != "fps": | ||
37 | trim_doc[ 'fps' ] = partial_doc[ 'fps' ] | ||
38 | trim_doc[ '/total_time' ] = partial_doc[ '/total_time' ] | ||
39 | trim_doc[ 'utc_time' ] = partial_doc[ 'utc_time' ] | ||
40 | full_doc.append(trim_doc) | ||
41 | |||
42 | sourcefile.close() | ||
43 | return full_doc | ||
44 | |||
45 | # Extract just the meta info line, and the timestamp of the first/last frame entry. | ||
46 | def parse_logfile_info(filename, verbose=False): | ||
47 | # Open source temp log file. Let exceptions percolate up. | ||
48 | sourcefile = open(filename, 'rU') # U is to open with Universal newline support | ||
49 | |||
50 | if verbose: | ||
51 | print "Reading " + filename | ||
52 | |||
53 | # The first line is the meta info line. | ||
54 | info_line = sourcefile.readline() | ||
55 | if not info_line: | ||
56 | sourcefile.close() | ||
57 | return None | ||
58 | |||
59 | # The rest of the lines are frames. Read the first and last to get the time range. | ||
60 | info = llsd.parse( info_line ) | ||
61 | info['start_time'] = None | ||
62 | info['end_time'] = None | ||
63 | first_frame = sourcefile.readline() | ||
64 | if first_frame: | ||
65 | try: | ||
66 | info['start_time'] = int(llsd.parse(first_frame)['timestamp']) | ||
67 | except: | ||
68 | pass | ||
69 | |||
70 | # Read the file backwards to find the last two lines. | ||
71 | sourcefile.seek(0, 2) | ||
72 | file_size = sourcefile.tell() | ||
73 | offset = 1024 | ||
74 | num_attempts = 0 | ||
75 | end_time = None | ||
76 | if file_size < offset: | ||
77 | offset = file_size | ||
78 | while 1: | ||
79 | sourcefile.seek(-1*offset, 2) | ||
80 | read_str = sourcefile.read(offset) | ||
81 | # Remove newline at the end | ||
82 | if read_str[offset - 1] == '\n': | ||
83 | read_str = read_str[0:-1] | ||
84 | lines = read_str.split('\n') | ||
85 | full_line = None | ||
86 | if len(lines) > 2: # Got two line | ||
87 | try: | ||
88 | end_time = llsd.parse(lines[-1])['timestamp'] | ||
89 | except: | ||
90 | # We couldn't parse this line. Try once more. | ||
91 | try: | ||
92 | end_time = llsd.parse(lines[-2])['timestamp'] | ||
93 | except: | ||
94 | # Nope. Just move on. | ||
95 | pass | ||
96 | break | ||
97 | if len(read_str) == file_size: # Reached the beginning | ||
98 | break | ||
99 | offset += 1024 | ||
100 | |||
101 | info['end_time'] = int(end_time) | ||
102 | |||
103 | sourcefile.close() | ||
104 | return info | ||
105 | |||
106 | |||
107 | def parse_proc_filename(filename): | ||
108 | try: | ||
109 | name_as_list = filename.split(".") | ||
110 | cur_stat_type = name_as_list[0].split("_")[0] | ||
111 | cur_pid = name_as_list[1] | ||
112 | except IndexError, ValueError: | ||
113 | return (None, None) | ||
114 | return (cur_pid, cur_stat_type) | ||
115 | |||
116 | # ---------------------------------------------------- | ||
117 | def get_simstats_list(path=None): | ||
118 | """ Return stats (pid, type) listed in <type>_proc.<pid>.llsd """ | ||
119 | if path is None: | ||
120 | path = DEFAULT_PATH | ||
121 | simstats_list = [] | ||
122 | for file_name in os.listdir(path): | ||
123 | if file_name.endswith(".llsd") and file_name != "simperf_proc_config.llsd": | ||
124 | simstats_info = parse_logfile_info(path + file_name) | ||
125 | if simstats_info is not None: | ||
126 | simstats_list.append(simstats_info) | ||
127 | return simstats_list | ||
128 | |||
129 | def get_log_info_list(pid=None, stat_type=None, path=None, target_column=None, verbose=False): | ||
130 | """ Return data from all llsd files matching the pid and stat type """ | ||
131 | if path is None: | ||
132 | path = DEFAULT_PATH | ||
133 | log_info_list = {} | ||
134 | for file_name in os.listdir ( path ): | ||
135 | if file_name.endswith(".llsd") and file_name != "simperf_proc_config.llsd": | ||
136 | (cur_pid, cur_stat_type) = parse_proc_filename(file_name) | ||
137 | if cur_pid is None: | ||
138 | continue | ||
139 | if pid is not None and pid != cur_pid: | ||
140 | continue | ||
141 | if stat_type is not None and stat_type != cur_stat_type: | ||
142 | continue | ||
143 | log_info_list[cur_pid] = parse_logfile(path + file_name, target_column, verbose) | ||
144 | return log_info_list | ||
145 | |||
146 | def delete_simstats_files(pid=None, stat_type=None, path=None): | ||
147 | """ Delete *.<pid>.llsd files """ | ||
148 | if path is None: | ||
149 | path = DEFAULT_PATH | ||
150 | del_list = [] | ||
151 | for file_name in os.listdir(path): | ||
152 | if file_name.endswith(".llsd") and file_name != "simperf_proc_config.llsd": | ||
153 | (cur_pid, cur_stat_type) = parse_proc_filename(file_name) | ||
154 | if cur_pid is None: | ||
155 | continue | ||
156 | if pid is not None and pid != cur_pid: | ||
157 | continue | ||
158 | if stat_type is not None and stat_type != cur_stat_type: | ||
159 | continue | ||
160 | del_list.append(cur_pid) | ||
161 | # Allow delete related exceptions to percolate up if this fails. | ||
162 | os.unlink(os.path.join(DEFAULT_PATH, file_name)) | ||
163 | return del_list | ||
164 | |||