def test_default(self): tree = {'a': 1} assert treelib.tree_get(tree, 'b', default=10) == 10 assert treelib.tree_get(tree, 'b.c.d', default=10) == 10 tree = {'a': []} assert treelib.tree_get(tree, 'a.[1]', default=10) == 10 assert treelib.tree_get(tree, 'a.[1].b.c.d', default=10) == 10
def test_default(self): tree = {'a': 1} assert treelib.tree_get(tree, 'b', default=10) == 10 assert treelib.tree_get(tree, 'b.c.d', default=10) == 10 tree = {'a': []} assert treelib.tree_get(tree, 'a.[1]', default=10) == 10 assert treelib.tree_get(tree, 'a.[1].b.c.d', default=10) == 10
def test_edge_missing(self): tree = {'a': 1} with pytest.raises(KeyError) as exc_info: assert treelib.tree_get(tree, 'b') exc_str = str(exc_info.exconly()) assert exc_str == 'KeyError: \'b\''
def test_index_missing(self): tree = {'a': []} with pytest.raises(IndexError) as exc_info: assert treelib.tree_get(tree, 'a.[1]') exc_str = str(exc_info.exconly()) assert exc_str == 'IndexError: list index out of range'
def test_edge_missing(self): tree = {'a': 1} with pytest.raises(KeyError) as exc_info: assert treelib.tree_get(tree, 'b') exc_str = str(exc_info.exconly()) assert exc_str == 'KeyError: \'b\''
def test_index_missing(self): tree = {'a': []} with pytest.raises(IndexError) as exc_info: assert treelib.tree_get(tree, 'a.[1]') exc_str = str(exc_info.exconly()) assert exc_str == 'IndexError: list index out of range'
def action(self, raw_crash, processed_crash, notes): notes.append( 'Signature replaced with a JIT Crash Category, ' 'was: "{}"'.format(processed_crash.get('signature', '')) ) processed_crash['signature'] = "jit | {}".format( tree_get(processed_crash, 'classifications.jit.category') ) return True
def action(self, raw_crash, processed_crash, notes): notes.append( 'Signature replaced with a JIT Crash Category, ' 'was: "{}"'.format(processed_crash.get('signature', '')) ) processed_crash['signature'] = "jit | {}".format( tree_get(processed_crash, 'classifications.jit.category') ) return True
def action(self, raw_crash, processed_crash, notes): # If this is a Java crash, then generate a Java signature if raw_crash.get('JavaStackTrace', None): signature, signature_notes = self.java_signature_tool.generate( raw_crash['JavaStackTrace'], delimiter=': ' ) processed_crash['signature'] = signature if signature_notes: notes.extend(signature_notes) return True # This isn't a Java crash, so figure out what we need and then generate a C signature crashed_thread = self._get_crashing_thread(processed_crash) try: if processed_crash.get('hang_type', None) == 1: # Force the signature to come from thread 0 signature_list = self._create_frame_list( tree_get(processed_crash, 'json_dump.threads.[0]'), tree_get(processed_crash, 'json_dump.system_info.os') == 'Windows NT' ) elif crashed_thread is not None: signature_list = self._create_frame_list( tree_get(processed_crash, 'json_dump.threads.[%d]' % crashed_thread), tree_get(processed_crash, 'json_dump.system_info.os') == 'Windows NT' ) else: signature_list = [] except (KeyError, IndexError) as exc: notes.append('No crashing frames found because of %s' % exc) signature_list = [] signature, signature_notes = self.c_signature_tool.generate( signature_list, processed_crash.get('hang_type', None), crashed_thread, ) processed_crash['proto_signature'] = ' | '.join(signature_list) processed_crash['signature'] = signature if signature_notes: notes.extend(signature_notes) return True
def action(self, raw_crash, processed_crash, notes): # If this is a Java crash, then generate a Java signature if raw_crash.get('JavaStackTrace', None): signature, signature_notes = self.java_signature_tool.generate( raw_crash['JavaStackTrace'], delimiter=': ' ) processed_crash['signature'] = signature if signature_notes: notes.extend(signature_notes) return True # This isn't a Java crash, so figure out what we need and then generate a C signature crashed_thread = self._get_crashing_thread(processed_crash) try: if processed_crash.get('hang_type', None) == 1: # Force the signature to come from thread 0 signature_list = self._create_frame_list( tree_get(processed_crash, 'json_dump.threads.[0]'), tree_get(processed_crash, 'json_dump.system_info.os') == 'Windows NT' ) elif crashed_thread is not None: signature_list = self._create_frame_list( tree_get(processed_crash, 'json_dump.threads.[%d]' % crashed_thread), tree_get(processed_crash, 'json_dump.system_info.os') == 'Windows NT' ) else: signature_list = [] except (KeyError, IndexError) as exc: notes.append('No crashing frames found because of %s' % exc) signature_list = [] signature, signature_notes = self.c_signature_tool.generate( signature_list, processed_crash.get('hang_type', None), crashed_thread, ) processed_crash['proto_signature'] = ' | '.join(signature_list) processed_crash['signature'] = signature if signature_notes: notes.extend(signature_notes) return True
def _get_crashing_thread(self, processed_crash): return tree_get(processed_crash, 'json_dump.crash_info.crashing_thread', default=None)
def test_good_gets(self, tree, path, expected): assert treelib.tree_get(tree, path) == expected
def main(args): """Takes crash data via args and generates a Socorro signature """ parser = argparse.ArgumentParser(description=DESCRIPTION, epilog=EPILOG) parser.add_argument( '-v', '--verbose', help='increase output verbosity', action='store_true' ) parser.add_argument( '--format', help='specify output format: csv, text (default)' ) parser.add_argument( 'crashids', metavar='crashid', nargs='+', help='crash id to generate signatures for' ) args = parser.parse_args() if args.format == 'csv': outputter = CSVOutput else: outputter = TextOutput if args.verbose: logging_level = 'DEBUG' else: logging_level = 'INFO' api_token = os.environ.get('SOCORRO_API_TOKEN', '') setup_logging(logging_level) generator = SignatureGenerator(debug=args.verbose) with outputter() as out: for crash_id in args.crashids: resp = fetch('/RawCrash/', crash_id, api_token) if resp.status_code == 404: out.warning('%s: does not exist.' % crash_id) continue if resp.status_code == 429: out.warning('API rate limit reached. %s' % resp.content) # FIXME(willkg): Maybe there's something better we could do here. Like maybe wait a # few minutes. return 1 if resp.status_code == 500: out.warning('HTTP 500: %s' % resp.content) continue raw_crash = resp.json() # If there's an error in the raw crash, then something is wrong--probably with the API # token. So print that out and exit. if 'error' in raw_crash: out.warning('Error fetching raw crash: %s' % raw_crash['error']) return 1 raw_crash_minimal = { 'JavaStackTrace': raw_crash.get('JavaStackTrace', None), 'OOMAllocationSize': raw_crash.get('OOMAllocationSize', None), 'AbortMessage': raw_crash.get('AbortMessage', None), 'AsyncShutdownTimeout': raw_crash.get('AsyncShutdownTimeout', None), 'ipc_channel_error': raw_crash.get('ipc_channel_error', None), 'additional_minidumps': raw_crash.get('additional_minidumps', None), 'IPCMessageName': raw_crash.get('IPCMessageName', None), } resp = fetch('/ProcessedCrash/', crash_id, api_token) if resp.status_code == 404: out.warning('%s: does not have processed crash.' % crash_id) continue if resp.status_code == 429: out.warning('API rate limit reached. %s' % resp.content) # FIXME(willkg): Maybe there's something better we could do here. Like maybe wait a # few minutes. return 1 if resp.status_code == 500: out.warning('HTTP 500: %s' % resp.content) continue processed_crash = resp.json() # If there's an error in the processed crash, then something is wrong--probably with the # API token. So print that out and exit. if 'error' in processed_crash: out.warning('Error fetching processed crash: %s' % processed_crash['error']) return 1 old_signature = processed_crash['signature'] processed_crash_minimal = { 'hang_type': processed_crash.get('hang_type', None), 'json_dump': { 'threads': tree_get(processed_crash, 'json_dump.threads', default=[]), 'system_info': { 'os': tree_get(processed_crash, 'json_dump.system_info.os', default=''), }, 'crash_info': { 'crashing_thread': tree_get( processed_crash, 'json_dump.crash_info.crashing_thread', default=None ), }, }, # NOTE(willkg): Classifications aren't available via the public API. 'classifications': { 'jit': { 'category': tree_get(processed_crash, 'classifications.jit.category', ''), }, }, 'mdsw_status_string': processed_crash.get('mdsw_status_string', None), # This needs to be an empty string--the signature generator fills it in. 'signature': '' } ret = generator.generate(raw_crash_minimal, processed_crash_minimal) out.data(crash_id, old_signature, ret['signature'], ret['notes'])
def predicate(self, raw_crash, processed_crash): return bool(tree_get(processed_crash, 'classifications.jit.category', default=None))
def _get_crashing_thread(self, processed_crash): return tree_get(processed_crash, 'json_dump.crash_info.crashing_thread', default=None)
def predicate(self, raw_crash, processed_crash): return bool(tree_get(processed_crash, 'classifications.jit.category', default=None))
def main(args): """Takes crash data via args and generates a Socorro signature """ parser = argparse.ArgumentParser(description=DESCRIPTION, epilog=EPILOG) parser.add_argument('-v', '--verbose', help='increase output verbosity', action='store_true') parser.add_argument('--format', help='specify output format: csv, text (default)') parser.add_argument( '--different-only', dest='different', action='store_true', help='limit output to just the signatures that changed', ) parser.add_argument('crashids', metavar='crashid', nargs='*', help='crash id to generate signatures for') args = parser.parse_args() if args.format == 'csv': outputter = CSVOutput else: outputter = TextOutput if args.verbose: logging_level = 'DEBUG' else: logging_level = 'INFO' api_token = os.environ.get('SOCORRO_API_TOKEN', '') setup_logging(logging_level) generator = SignatureGenerator(debug=args.verbose) crashids_iterable = args.crashids or sys.stdin with outputter() as out: for crash_id in crashids_iterable: crash_id = crash_id.strip() resp = fetch('/RawCrash/', crash_id, api_token) if resp.status_code == 404: out.warning('%s: does not exist.' % crash_id) continue if resp.status_code == 429: out.warning('API rate limit reached. %s' % resp.content) # FIXME(willkg): Maybe there's something better we could do here. Like maybe wait a # few minutes. return 1 if resp.status_code == 500: out.warning('HTTP 500: %s' % resp.content) continue raw_crash = resp.json() # If there's an error in the raw crash, then something is wrong--probably with the API # token. So print that out and exit. if 'error' in raw_crash: out.warning('Error fetching raw crash: %s' % raw_crash['error']) return 1 raw_crash_minimal = { 'JavaStackTrace': raw_crash.get('JavaStackTrace', None), 'OOMAllocationSize': raw_crash.get('OOMAllocationSize', None), 'AbortMessage': raw_crash.get('AbortMessage', None), 'AsyncShutdownTimeout': raw_crash.get('AsyncShutdownTimeout', None), 'ipc_channel_error': raw_crash.get('ipc_channel_error', None), 'additional_minidumps': raw_crash.get('additional_minidumps', None), 'IPCMessageName': raw_crash.get('IPCMessageName', None), 'MozCrashReason': raw_crash.get('MozCrashReason', None), } resp = fetch('/ProcessedCrash/', crash_id, api_token) if resp.status_code == 404: out.warning('%s: does not have processed crash.' % crash_id) continue if resp.status_code == 429: out.warning('API rate limit reached. %s' % resp.content) # FIXME(willkg): Maybe there's something better we could do here. Like maybe wait a # few minutes. return 1 if resp.status_code == 500: out.warning('HTTP 500: %s' % resp.content) continue processed_crash = resp.json() # If there's an error in the processed crash, then something is wrong--probably with the # API token. So print that out and exit. if 'error' in processed_crash: out.warning('Error fetching processed crash: %s' % processed_crash['error']) return 1 old_signature = processed_crash['signature'] processed_crash_minimal = { 'hang_type': processed_crash.get('hang_type', None), 'json_dump': { 'threads': tree_get(processed_crash, 'json_dump.threads', default=[]), 'system_info': { 'os': tree_get(processed_crash, 'json_dump.system_info.os', default=''), }, 'crash_info': { 'crashing_thread': tree_get(processed_crash, 'json_dump.crash_info.crashing_thread', default=None), }, }, # NOTE(willkg): Classifications aren't available via the public API. 'classifications': { 'jit': { 'category': tree_get(processed_crash, 'classifications.jit.category', ''), }, }, 'mdsw_status_string': processed_crash.get('mdsw_status_string', None), # This needs to be an empty string--the signature generator fills it in. 'signature': '' } # We want to generate fresh signatures, so we remove the "normalized" field from stack # frames because this is essentially cached data from processing for thread in processed_crash_minimal['json_dump'].get( 'threads', []): for frame in thread.get('frames', []): if 'normalized' in frame: del frame['normalized'] ret = generator.generate(raw_crash_minimal, processed_crash_minimal) if not args.different or old_signature != ret['signature']: out.data(crash_id, old_signature, ret['signature'], ret['notes'])
def test_good_gets(self, tree, path, expected): assert treelib.tree_get(tree, path) == expected