diff options
| author | Don Kjer <don@lindenlab.com> | 2008-10-09 18:07:46 +0000 | 
|---|---|---|
| committer | Don Kjer <don@lindenlab.com> | 2008-10-09 18:07:46 +0000 | 
| commit | 4ff16b735f59326514ad92ec38e3261cd996e05c (patch) | |
| tree | 170416c912dc272e7e171f156494946e05444e55 /indra/lib | |
| parent | b807e3df990e6fad25cd0bca94d2959dac042b13 (diff) | |
QAR-907: svn merge -r 98908:98910 svn+ssh://svn/svn/linden/qa/sim-metrics/sim-metrics2-release-merge-98903 into release
Diffstat (limited to 'indra/lib')
| -rwxr-xr-x | indra/lib/python/indra/util/llperformance.py | 158 | ||||
| -rwxr-xr-x | indra/lib/python/indra/util/simperf_host_xml_parser.py | 338 | ||||
| -rwxr-xr-x | indra/lib/python/indra/util/simperf_oprof_interface.py | 160 | ||||
| -rwxr-xr-x | indra/lib/python/indra/util/simperf_proc_interface.py | 164 | 
4 files changed, 820 insertions, 0 deletions
diff --git a/indra/lib/python/indra/util/llperformance.py b/indra/lib/python/indra/util/llperformance.py new file mode 100755 index 0000000000..7c52730b5e --- /dev/null +++ b/indra/lib/python/indra/util/llperformance.py @@ -0,0 +1,158 @@ +#!/usr/bin/python + +# ------------------------------------------------ +# Sim metrics utility functions. + +import glob, os, time, sys, stat, exceptions + +from indra.base import llsd + +gBlockMap = {}              #Map of performance metric data with function hierarchy information. +gCurrentStatPath = "" + +gIsLoggingEnabled=False + +class LLPerfStat: +    def __init__(self,key): +        self.mTotalTime = 0 +        self.mNumRuns = 0 +        self.mName=key +        self.mTimeStamp = int(time.time()*1000) +        self.mUTCTime = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + +    def __str__(self): +        return "%f" %  self.mTotalTime + +    def start(self): +        self.mStartTime = int(time.time() * 1000000) +        self.mNumRuns += 1 + +    def stop(self): +        execution_time = int(time.time() * 1000000) - self.mStartTime +        self.mTotalTime += execution_time + +    def get_map(self): +        results={} +        results['name']=self.mName +        results['utc_time']=self.mUTCTime +        results['timestamp']=self.mTimeStamp +        results['us']=self.mTotalTime +        results['count']=self.mNumRuns +        return results + +class PerfError(exceptions.Exception): +    def __init__(self): +        return + +    def __Str__(self): +        print "","Unfinished LLPerfBlock" + +class LLPerfBlock: +    def __init__( self, key ): +        global gBlockMap +        global gCurrentStatPath +        global gIsLoggingEnabled + +        #Check to see if we're running metrics right now. +        if gIsLoggingEnabled: +            self.mRunning = True        #Mark myself as running. +     +            self.mPreviousStatPath = gCurrentStatPath +            gCurrentStatPath += "/" + key +            if gCurrentStatPath not in gBlockMap: +                gBlockMap[gCurrentStatPath] = LLPerfStat(key) + +            self.mStat = gBlockMap[gCurrentStatPath] +            self.mStat.start() +     +    def finish( self ): +        global gBlockMap +        global gIsLoggingEnabled + +        if gIsLoggingEnabled: +            self.mStat.stop() +            self.mRunning = False +            gCurrentStatPath = self.mPreviousStatPath + +#    def __del__( self ): +#        if self.mRunning: +#            #SPATTERS FIXME +#            raise PerfError + +class LLPerformance: +    #-------------------------------------------------- +    # Determine whether or not we want to log statistics + +    def __init__( self, process_name = "python" ): +        self.process_name = process_name +        self.init_testing() +        self.mTimeStamp = int(time.time()*1000) +        self.mUTCTime = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + +    def init_testing( self ): +        global gIsLoggingEnabled + +        host_performance_file = "/dev/shm/simperf/simperf_proc_config.llsd" +     +        #If file exists, open +        if os.path.exists(host_performance_file): +            file = open (host_performance_file,'r') +     +            #Read serialized LLSD from file. +            body = llsd.parse(file.read()) +     +            #Calculate time since file last modified. +            stats = os.stat(host_performance_file) +            now = time.time() +            mod = stats[stat.ST_MTIME] +            age = now - mod +     +            if age < ( body['duration'] ): +                gIsLoggingEnabled = True +     + +    def get ( self ): +        global gIsLoggingEnabled +        return gIsLoggingEnabled + +    #def output(self,ptr,path): +    #    if 'stats' in ptr: +    #        stats = ptr['stats'] +    #        self.mOutputPtr[path] = stats.get_map() + +    #    if 'children' in ptr: +    #        children=ptr['children'] + +    #        curptr = self.mOutputPtr +    #        curchildren={} +    #        curptr['children'] = curchildren + +    #        for key in children: +    #            curchildren[key]={} +    #            self.mOutputPtr = curchildren[key] +    #            self.output(children[key],path + '/' + key) +     +    def done(self): +        global gBlockMap + +        if not self.get(): +            return + +        output_name = "/dev/shm/simperf/%s_proc.%d.llsd" % (self.process_name, os.getpid()) +        output_file = open(output_name, 'w') +        process_info = { +            "name"  :   self.process_name, +            "pid"   :   os.getpid(), +            "ppid"  :   os.getppid(), +            "timestamp" :   self.mTimeStamp, +            "utc_time"  :   self.mUTCTime, +            } +        output_file.write(llsd.format_notation(process_info)) +        output_file.write('\n') + +        for key in gBlockMap.keys(): +            gBlockMap[key] = gBlockMap[key].get_map() +        output_file.write(llsd.format_notation(gBlockMap)) +        output_file.write('\n') +        output_file.close() + diff --git a/indra/lib/python/indra/util/simperf_host_xml_parser.py b/indra/lib/python/indra/util/simperf_host_xml_parser.py new file mode 100755 index 0000000000..b6084151c9 --- /dev/null +++ b/indra/lib/python/indra/util/simperf_host_xml_parser.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python +"""\ +@file simperf_host_xml_parser.py +@brief Digest collector's XML dump and convert to simple dict/list structure + +$LicenseInfo:firstyear=2008&license=mit$ + +Copyright (c) 2008, Linden Research, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +$/LicenseInfo$ +""" + +import sys, os, getopt, time +import simplejson +from xml import sax + + +def usage(): +    print "Usage:" +    print sys.argv[0] + " [options]" +    print "  Convert RRD's XML dump to JSON.  Script to convert the simperf_host_collector-" +    print "  generated RRD dump into JSON.  Steps include converting selected named" +    print "  fields from GAUGE type to COUNTER type by computing delta with preceding" +    print "  values.  Top-level named fields are:" +    print  +    print "     lastupdate      Time (javascript timestamp) of last data sample" +    print "     step            Time in seconds between samples" +    print "     ds              Data specification (name/type) for each column" +    print "     database        Table of data samples, one time step per row" +    print  +    print "Options:" +    print "  -i, --in      Input settings filename.  (Default:  stdin)" +    print "  -o, --out     Output settings filename.  (Default:  stdout)" +    print "  -h, --help    Print this message and exit." +    print +    print "Example: %s -i rrddump.xml -o rrddump.json" % sys.argv[0] +    print +    print "Interfaces:" +    print "   class SimPerfHostXMLParser()         # SAX content handler" +    print "   def simperf_host_xml_fixup(parser)   # post-parse value fixup" + +class SimPerfHostXMLParser(sax.handler.ContentHandler): + +    def __init__(self): +        pass +         +    def startDocument(self): +        self.rrd_last_update = 0         # public +        self.rrd_step = 0                # public +        self.rrd_ds = []                 # public +        self.rrd_records = []            # public +        self._rrd_level = 0 +        self._rrd_parse_state = 0 +        self._rrd_chars = "" +        self._rrd_capture = False +        self._rrd_ds_val = {} +        self._rrd_data_row = [] +        self._rrd_data_row_has_nan = False +         +    def endDocument(self): +        pass + +    # Nasty little ad-hoc state machine to extract the elements that are +    # necessary from the 'rrdtool dump' XML output.  The same element +    # name '<ds>' is used for two different data sets so we need to pay +    # some attention to the actual structure to get the ones we want +    # and ignore the ones we don't. +     +    def startElement(self, name, attrs): +        self._rrd_level = self._rrd_level + 1 +        self._rrd_capture = False +        if self._rrd_level == 1: +            if name == "rrd" and self._rrd_parse_state == 0: +                self._rrd_parse_state = 1     # In <rrd> +                self._rrd_capture = True +                self._rrd_chars = "" +        elif self._rrd_level == 2: +            if self._rrd_parse_state == 1: +                if name == "lastupdate": +                    self._rrd_parse_state = 2         # In <rrd><lastupdate> +                    self._rrd_capture = True +                    self._rrd_chars = "" +                elif name == "step": +                    self._rrd_parse_state = 3         # In <rrd><step> +                    self._rrd_capture = True +                    self._rrd_chars = "" +                elif name == "ds": +                    self._rrd_parse_state = 4         # In <rrd><ds> +                    self._rrd_ds_val = {} +                    self._rrd_chars = "" +                elif name == "rra": +                    self._rrd_parse_state = 5         # In <rrd><rra> +        elif self._rrd_level == 3: +            if self._rrd_parse_state == 4: +                if name == "name": +                    self._rrd_parse_state = 6         # In <rrd><ds><name> +                    self._rrd_capture = True +                    self._rrd_chars = "" +                elif name == "type": +                    self._rrd_parse_state = 7         # In <rrd><ds><type> +                    self._rrd_capture = True +                    self._rrd_chars = "" +            elif self._rrd_parse_state == 5: +                if name == "database": +                    self._rrd_parse_state = 8         # In <rrd><rra><database> +        elif self._rrd_level == 4: +            if self._rrd_parse_state == 8: +                if name == "row": +                    self._rrd_parse_state = 9         # In <rrd><rra><database><row> +                    self._rrd_data_row = [] +                    self._rrd_data_row_has_nan = False +        elif self._rrd_level == 5: +            if self._rrd_parse_state == 9: +                if name == "v": +                    self._rrd_parse_state = 10        # In <rrd><rra><database><row><v> +                    self._rrd_capture = True +                    self._rrd_chars = "" + +    def endElement(self, name): +        self._rrd_capture = False +        if self._rrd_parse_state == 10: +            self._rrd_capture = self._rrd_level == 6 +            if self._rrd_level == 5: +                if self._rrd_chars == "NaN": +                    self._rrd_data_row_has_nan = True +                else: +                    self._rrd_data_row.append(self._rrd_chars) +                self._rrd_parse_state = 9              # In <rrd><rra><database><row> +        elif self._rrd_parse_state == 9: +            if self._rrd_level == 4: +                if not self._rrd_data_row_has_nan: +                    self.rrd_records.append(self._rrd_data_row) +                self._rrd_parse_state = 8              # In <rrd><rra><database> +        elif self._rrd_parse_state == 8: +            if self._rrd_level == 3: +                self._rrd_parse_state = 5              # In <rrd><rra> +        elif self._rrd_parse_state == 7: +            if self._rrd_level == 3: +                self._rrd_ds_val["type"] = self._rrd_chars +                self._rrd_parse_state = 4              # In <rrd><ds> +        elif self._rrd_parse_state == 6: +            if self._rrd_level == 3: +                self._rrd_ds_val["name"] = self._rrd_chars +                self._rrd_parse_state = 4              # In <rrd><ds> +        elif self._rrd_parse_state == 5: +            if self._rrd_level == 2: +                self._rrd_parse_state = 1              # In <rrd> +        elif self._rrd_parse_state == 4: +            if self._rrd_level == 2: +                self.rrd_ds.append(self._rrd_ds_val) +                self._rrd_parse_state = 1              # In <rrd> +        elif self._rrd_parse_state == 3: +            if self._rrd_level == 2: +                self.rrd_step = long(self._rrd_chars) +                self._rrd_parse_state = 1              # In <rrd> +        elif self._rrd_parse_state == 2: +            if self._rrd_level == 2: +                self.rrd_last_update = long(self._rrd_chars) +                self._rrd_parse_state = 1              # In <rrd> +        elif self._rrd_parse_state == 1: +            if self._rrd_level == 1: +                self._rrd_parse_state = 0              # At top +                 +        if self._rrd_level: +            self._rrd_level = self._rrd_level - 1 + +    def characters(self, content): +        if self._rrd_capture: +            self._rrd_chars = self._rrd_chars + content.strip() + +def _make_numeric(value): +    try: +        value = float(value) +    except: +        value = "" +    return value + +def simperf_host_xml_fixup(parser, filter_start_time = None, filter_end_time = None): +    # Fixup for GAUGE fields that are really COUNTS.  They +    # were forced to GAUGE to try to disable rrdtool's +    # data interpolation/extrapolation for non-uniform time +    # samples. +    fixup_tags = [ "cpu_user", +                   "cpu_nice", +                   "cpu_sys", +                   "cpu_idle", +                   "cpu_waitio", +                   "cpu_intr", +                   # "file_active", +                   # "file_free", +                   # "inode_active", +                   # "inode_free", +                   "netif_in_kb", +                   "netif_in_pkts", +                   "netif_in_errs", +                   "netif_in_drop", +                   "netif_out_kb", +                   "netif_out_pkts", +                   "netif_out_errs", +                   "netif_out_drop", +                   "vm_page_in", +                   "vm_page_out", +                   "vm_swap_in", +                   "vm_swap_out", +                   #"vm_mem_total", +                   #"vm_mem_used", +                   #"vm_mem_active", +                   #"vm_mem_inactive", +                   #"vm_mem_free", +                   #"vm_mem_buffer", +                   #"vm_swap_cache", +                   #"vm_swap_total", +                   #"vm_swap_used", +                   #"vm_swap_free", +                   "cpu_interrupts", +                   "cpu_switches", +                   "cpu_forks" ] + +    col_count = len(parser.rrd_ds) +    row_count = len(parser.rrd_records) + +    # Process the last row separately, just to make all values numeric. +    for j in range(col_count): +        parser.rrd_records[row_count - 1][j] = _make_numeric(parser.rrd_records[row_count - 1][j]) + +    # Process all other row/columns. +    last_different_row = row_count - 1 +    current_row = row_count - 2 +    while current_row >= 0: +        # Check for a different value than the previous row.  If everything is the same +        # then this is probably just a filler/bogus entry. +        is_different = False +        for j in range(col_count): +            parser.rrd_records[current_row][j] = _make_numeric(parser.rrd_records[current_row][j]) +            if parser.rrd_records[current_row][j] != parser.rrd_records[last_different_row][j]: +                # We're good.  This is a different row. +                is_different = True + +        if not is_different: +            # This is a filler/bogus entry.  Just ignore it. +            for j in range(col_count): +                parser.rrd_records[current_row][j] = float('nan') +        else: +            # Some tags need to be converted into deltas. +            for j in range(col_count): +                if parser.rrd_ds[j]["name"] in fixup_tags: +                    parser.rrd_records[last_different_row][j] = \ +                        parser.rrd_records[last_different_row][j] - parser.rrd_records[current_row][j] +            last_different_row = current_row + +        current_row -= 1 + +    # Set fixup_tags in the first row to 'nan' since they aren't useful anymore. +    for j in range(col_count): +        if parser.rrd_ds[j]["name"] in fixup_tags: +            parser.rrd_records[0][j] = float('nan') + +    # Add a timestamp to each row and to the catalog.  Format and name +    # chosen to match other simulator logging (hopefully). +    start_time = parser.rrd_last_update - (parser.rrd_step * (row_count - 1)) +    # Build a filtered list of rrd_records if we are limited to a time range. +    filter_records = False +    if filter_start_time is not None or filter_end_time is not None: +        filter_records = True +        filtered_rrd_records = [] +        if filter_start_time is None: +            filter_start_time = start_time * 1000 +        if filter_end_time is None: +            filter_end_time = parser.rrd_last_update * 1000 +         +    for i in range(row_count): +        record_timestamp = (start_time + (i * parser.rrd_step)) * 1000 +        parser.rrd_records[i].insert(0, record_timestamp) +        if filter_records: +            if filter_start_time <= record_timestamp and record_timestamp <= filter_end_time: +                filtered_rrd_records.append(parser.rrd_records[i]) + +    if filter_records: +        parser.rrd_records = filtered_rrd_records + +    parser.rrd_ds.insert(0, {"type": "GAUGE", "name": "javascript_timestamp"}) + + +def main(argv=None): +    opts, args = getopt.getopt(sys.argv[1:], "i:o:h", ["in=", "out=", "help"]) +    input_file = sys.stdin +    output_file = sys.stdout +    for o, a in opts: +        if o in ("-i", "--in"): +            input_file = open(a, 'r') +        if o in ("-o", "--out"): +            output_file = open(a, 'w') +        if o in ("-h", "--help"): +            usage() +            sys.exit(0) + +    # Using the SAX parser as it is at least 4X faster and far, far +    # smaller on this dataset than the DOM-based interface in xml.dom.minidom. +    # With SAX and a 5.4MB xml file, this requires about seven seconds of +    # wall-clock time and 32MB VSZ.  With the DOM interface, about 22 seconds +    # and over 270MB VSZ. + +    handler = SimPerfHostXMLParser() +    sax.parse(input_file, handler) +    if input_file != sys.stdin: +        input_file.close() + +    # Various format fixups:  string-to-num, gauge-to-counts, add +    # a time stamp, etc. +    simperf_host_xml_fixup(handler) +     +    # Create JSONable dict with interesting data and format/print it +    print >>output_file, simplejson.dumps({ "step" : handler.rrd_step, +                                            "lastupdate": handler.rrd_last_update * 1000, +                                            "ds" : handler.rrd_ds, +                                            "database" : handler.rrd_records }) + +    return 0 + +if __name__ == "__main__": +    sys.exit(main()) diff --git a/indra/lib/python/indra/util/simperf_oprof_interface.py b/indra/lib/python/indra/util/simperf_oprof_interface.py new file mode 100755 index 0000000000..a7e9a4cb32 --- /dev/null +++ b/indra/lib/python/indra/util/simperf_oprof_interface.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +"""\ +@file simperf_oprof_interface.py +@brief Manage OProfile data collection on a host + +$LicenseInfo:firstyear=2008&license=internal$ + +Copyright (c) 2008, Linden Research, Inc. + +The following source code is PROPRIETARY AND CONFIDENTIAL. Use of +this source code is governed by the Linden Lab Source Code Disclosure +Agreement ("Agreement") previously entered between you and Linden +Lab. By accessing, using, copying, modifying or distributing this +software, you acknowledge that you have been informed of your +obligations under the Agreement and agree to abide by those obligations. + +ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO +WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, +COMPLETENESS OR PERFORMANCE. +$/LicenseInfo$ +""" + +import sys, os, getopt +import simplejson + + +def usage(): +    print "Usage:" +    print sys.argv[0] + " [options]" +    print "  Digest the OProfile report forms that come out of the" +    print "  simperf_oprof_ctl program's -r/--report command.  The result" +    print "  is an array of dictionaires with the following keys:" +    print  +    print "     symbol        Name of sampled, calling, or called procedure" +    print "     file          Executable or library where symbol resides" +    print "     percentage    Percentage contribution to profile, calls or called" +    print "     samples       Sample count" +    print "     calls         Methods called by the method in question (full only)" +    print "     called_by     Methods calling the method (full only)" +    print  +    print "  For 'full' reports the two keys 'calls' and 'called_by' are" +    print "  themselves arrays of dictionaries based on the first four keys." +    print +    print "Return Codes:" +    print "  None.  Aggressively digests everything.  Will likely mung results" +    print "  if a program or library has whitespace in its name." +    print +    print "Options:" +    print "  -i, --in      Input settings filename.  (Default:  stdin)" +    print "  -o, --out     Output settings filename.  (Default:  stdout)" +    print "  -h, --help    Print this message and exit." +    print +    print "Interfaces:" +    print "   class SimPerfOProfileInterface()" +     +class SimPerfOProfileInterface: +    def __init__(self): +        self.isBrief = True             # public +        self.isValid = False            # public +        self.result = []                # public + +    def parse(self, input): +        in_samples = False +        for line in input: +            if in_samples: +                if line[0:6] == "------": +                    self.isBrief = False +                    self._parseFull(input) +                else: +                    self._parseBrief(input, line) +                self.isValid = True +                return +            try: +                hd1, remain = line.split(None, 1) +                if hd1 == "samples": +                    in_samples = True +            except ValueError: +                pass + +    def _parseBrief(self, input, line1): +        try: +            fld1, fld2, fld3, fld4 = line1.split(None, 3) +            self.result.append({"samples" : fld1, +                                "percentage" : fld2, +                                "file" : fld3, +                                "symbol" : fld4.strip("\n")}) +        except ValueError: +            pass +        for line in input: +            try: +                fld1, fld2, fld3, fld4 = line.split(None, 3) +                self.result.append({"samples" : fld1, +                                    "percentage" : fld2, +                                    "file" : fld3, +                                    "symbol" : fld4.strip("\n")}) +            except ValueError: +                pass + +    def _parseFull(self, input): +        state = 0       # In 'called_by' section +        calls = [] +        called_by = [] +        current = {} +        for line in input: +            if line[0:6] == "------": +                if len(current): +                    current["calls"] = calls +                    current["called_by"] = called_by +                    self.result.append(current) +                state = 0 +                calls = [] +                called_by = [] +                current = {} +            else: +                try: +                    fld1, fld2, fld3, fld4 = line.split(None, 3) +                    tmp = {"samples" : fld1, +                           "percentage" : fld2, +                           "file" : fld3, +                           "symbol" : fld4.strip("\n")} +                except ValueError: +                    continue +                if line[0] != " ": +                    current = tmp +                    state = 1       # In 'calls' section +                elif state == 0: +                    called_by.append(tmp) +                else: +                    calls.append(tmp) +        if len(current): +            current["calls"] = calls +            current["called_by"] = called_by +            self.result.append(current) + + +def main(argv=None): +    opts, args = getopt.getopt(sys.argv[1:], "i:o:h", ["in=", "out=", "help"]) +    input_file = sys.stdin +    output_file = sys.stdout +    for o, a in opts: +        if o in ("-i", "--in"): +            input_file = open(a, 'r') +        if o in ("-o", "--out"): +            output_file = open(a, 'w') +        if o in ("-h", "--help"): +            usage() +            sys.exit(0) + +    oprof = SimPerfOProfileInterface() +    oprof.parse(input_file) +    if input_file != sys.stdin: +        input_file.close() + +    # Create JSONable dict with interesting data and format/print it +    print >>output_file, simplejson.dumps(oprof.result) + +    return 0 + +if __name__ == "__main__": +    sys.exit(main()) diff --git a/indra/lib/python/indra/util/simperf_proc_interface.py b/indra/lib/python/indra/util/simperf_proc_interface.py new file mode 100755 index 0000000000..62a63fa872 --- /dev/null +++ b/indra/lib/python/indra/util/simperf_proc_interface.py @@ -0,0 +1,164 @@ +#!/usr/bin/python + +# ---------------------------------------------------- +# Utility to extract log messages from *.<pid>.llsd +# files that contain performance statistics. + +# ---------------------------------------------------- +import sys, os + +if os.path.exists("setup-path.py"): +    execfile("setup-path.py") + +from indra.base import llsd + +DEFAULT_PATH="/dev/shm/simperf/" + + +# ---------------------------------------------------- +# Pull out the stats and return a single document +def parse_logfile(filename, target_column=None, verbose=False): +    full_doc = [] +    # Open source temp log file.  Let exceptions percolate up. +    sourcefile = open( filename,'r') +         +    if verbose: +        print "Reading " + filename   +     +    # Parse and output all lines from the temp file +    for line in sourcefile.xreadlines(): +        partial_doc = llsd.parse(line) +        if partial_doc is not None: +            if target_column is None: +                full_doc.append(partial_doc) +            else: +                trim_doc = { target_column: partial_doc[target_column] } +                if target_column != "fps": +                    trim_doc[ 'fps' ] = partial_doc[ 'fps' ] +                trim_doc[ '/total_time' ] = partial_doc[ '/total_time' ] +                trim_doc[ 'utc_time' ] = partial_doc[ 'utc_time' ] +                full_doc.append(trim_doc) + +    sourcefile.close() +    return full_doc + +# Extract just the meta info line, and the timestamp of the first/last frame entry. +def parse_logfile_info(filename, verbose=False): +    # Open source temp log file.  Let exceptions percolate up. +    sourcefile = open(filename, 'rU') # U is to open with Universal newline support +         +    if verbose: +        print "Reading " + filename   + +    # The first line is the meta info line. +    info_line = sourcefile.readline() +    if not info_line: +        sourcefile.close() +        return None + +    # The rest of the lines are frames.  Read the first and last to get the time range. +    info = llsd.parse( info_line ) +    info['start_time'] = None +    info['end_time'] = None +    first_frame = sourcefile.readline() +    if first_frame: +        try: +            info['start_time'] = int(llsd.parse(first_frame)['timestamp']) +        except: +            pass + +    # Read the file backwards to find the last two lines. +    sourcefile.seek(0, 2) +    file_size = sourcefile.tell() +    offset = 1024 +    num_attempts = 0 +    end_time = None +    if file_size < offset: +        offset = file_size +    while 1: +        sourcefile.seek(-1*offset, 2) +        read_str = sourcefile.read(offset) +        # Remove newline at the end +        if read_str[offset - 1] == '\n': +            read_str = read_str[0:-1] +        lines = read_str.split('\n') +        full_line = None +        if len(lines) > 2:  # Got two line +            try: +                end_time = llsd.parse(lines[-1])['timestamp'] +            except: +                # We couldn't parse this line.  Try once more. +                try: +                    end_time = llsd.parse(lines[-2])['timestamp'] +                except: +                    # Nope.  Just move on. +                    pass +            break +        if len(read_str) == file_size:   # Reached the beginning +            break +        offset += 1024 + +    info['end_time'] = int(end_time) + +    sourcefile.close() +    return info +     + +def parse_proc_filename(filename): +    try: +        name_as_list = filename.split(".") +        cur_stat_type = name_as_list[0].split("_")[0] +        cur_pid = name_as_list[1] +    except IndexError, ValueError: +        return (None, None) +    return (cur_pid, cur_stat_type) + +# ---------------------------------------------------- +def get_simstats_list(path=None): +    """ Return stats (pid, type) listed in <type>_proc.<pid>.llsd """ +    if path is None: +        path = DEFAULT_PATH +    simstats_list = [] +    for file_name in os.listdir(path): +        if file_name.endswith(".llsd") and file_name != "simperf_proc_config.llsd": +            simstats_info = parse_logfile_info(path + file_name) +            if simstats_info is not None: +                simstats_list.append(simstats_info) +    return simstats_list + +def get_log_info_list(pid=None, stat_type=None, path=None, target_column=None, verbose=False): +    """ Return data from all llsd files matching the pid and stat type """ +    if path is None: +        path = DEFAULT_PATH +    log_info_list = {} +    for file_name in os.listdir ( path ): +        if file_name.endswith(".llsd") and file_name != "simperf_proc_config.llsd": +            (cur_pid, cur_stat_type) = parse_proc_filename(file_name) +            if cur_pid is None: +                continue +            if pid is not None and pid != cur_pid: +                continue +            if stat_type is not None and stat_type != cur_stat_type: +                continue +            log_info_list[cur_pid] = parse_logfile(path + file_name, target_column, verbose) +    return log_info_list + +def delete_simstats_files(pid=None, stat_type=None, path=None): +    """ Delete *.<pid>.llsd files """ +    if path is None: +        path = DEFAULT_PATH +    del_list = [] +    for file_name in os.listdir(path): +        if file_name.endswith(".llsd") and file_name != "simperf_proc_config.llsd": +            (cur_pid, cur_stat_type) = parse_proc_filename(file_name) +            if cur_pid is None: +                continue +            if pid is not None and pid != cur_pid: +                continue +            if stat_type is not None and stat_type != cur_stat_type: +                continue +            del_list.append(cur_pid) +            # Allow delete related exceptions to percolate up if this fails. +            os.unlink(os.path.join(DEFAULT_PATH, file_name)) +    return del_list +  | 
