|  | #!/usr/bin/env python | 
|  | # Copyright 2017 The Chromium Authors | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | import os | 
|  | import re | 
|  | import sys | 
|  |  | 
|  | kUsage = '''Usage: truncate_net_log.py INPUT_FILE OUTPUT_FILE TRUNCATED_SIZE | 
|  |  | 
|  | Creates a smaller version of INPUT_FILE (which is a chrome-net-export-log.json | 
|  | formatted NetLog file) and saves it to OUTPUT_FILE. Note that this works by | 
|  | reading the file line by line and not fully parsing the JSON, so it must match | 
|  | the exact format (whitespace and all). | 
|  |  | 
|  | File truncation is done by dropping the oldest events and keeping everything | 
|  | else. | 
|  |  | 
|  | Parameters: | 
|  |  | 
|  | INPUT_FILE: | 
|  | Path to net-export JSON file | 
|  |  | 
|  | OUTPUT_FILE: | 
|  | Path to save truncated file to | 
|  |  | 
|  | TRUNCATED_SIZE: | 
|  | The desired (approximate) size for the truncated file. May use a suffix to | 
|  | indicate units. Examples: | 
|  | 2003  -->  2003 bytes | 
|  | 100K  -->  100 KiB | 
|  | 8M    -->  8 MiB | 
|  | 1.5m  -->  1.5 MiB | 
|  | ''' | 
|  |  | 
|  | def get_file_size(path): | 
|  | '''Returns the filesize of |path| in bytes''' | 
|  | return os.stat(path).st_size | 
|  |  | 
|  |  | 
|  | def truncate_log_file(in_path, out_path, desired_size): | 
|  | '''Copies |in_path| to |out_path| such that it is approximately | 
|  | |desired_size| bytes large. This is accomplished by dropping the oldest | 
|  | events first. The final file size may not be exactly |desired_size| as only | 
|  | complete event lines are skipped.''' | 
|  | orig_size = get_file_size(in_path) | 
|  | bytes_to_truncate = orig_size - desired_size | 
|  |  | 
|  | # This variable is True if the current line being processed is an Event line. | 
|  | inside_events = False | 
|  | with open(out_path, 'w') as out_file: | 
|  | with open(in_path, 'r') as in_file: | 
|  | for line in in_file: | 
|  | # The final line before polledData closes the events array, and hence | 
|  | # ends in "],". The check for polledData is more for documentation | 
|  | # sake. | 
|  | if inside_events and (line.startswith('"polledData": {' or | 
|  | line.endswith('],\n'))): | 
|  | inside_events = False | 
|  |  | 
|  | # If this is an event line and need to drop more bytes, go ahead and | 
|  | # skip the line. Otherwise copy it to the output file. | 
|  | if inside_events and bytes_to_truncate > 0: | 
|  | bytes_to_truncate -= len(line) | 
|  | else: | 
|  | out_file.write(line) | 
|  |  | 
|  | # All lines after this are events (up until the closing square | 
|  | # bracket). | 
|  | if line.startswith('"events": ['): | 
|  | inside_events = True | 
|  |  | 
|  | sys.stdout.write( | 
|  | 'Truncated file from %d to %d bytes\n' % (orig_size, | 
|  | get_file_size(out_path))) | 
|  |  | 
|  | def parse_filesize_str(filesize_str): | 
|  | '''Parses a string representation of a file size into a byte value, or None | 
|  | on failure''' | 
|  | filesize_str = filesize_str.lower() | 
|  | m = re.match('([0-9\.]+)([km]?)', filesize_str) | 
|  |  | 
|  | if not m: | 
|  | return None | 
|  |  | 
|  | # Try to parse as decimal (regex above accepts some invalid decimals too). | 
|  | float_value = 0.0 | 
|  | try: | 
|  | float_value = float(m.group(1)) | 
|  | except ValueError: | 
|  | return None | 
|  |  | 
|  | kSuffixValueBytes = { | 
|  | 'k': 1024, | 
|  | 'm': 1024 * 1024, | 
|  | '': 1, | 
|  | } | 
|  |  | 
|  | suffix = m.group(2) | 
|  | return int(float_value * kSuffixValueBytes[suffix]) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | if len(sys.argv) != 4: | 
|  | sys.stderr.write('ERROR: Requires 3 command line arguments\n') | 
|  | sys.stderr.write(kUsage) | 
|  | sys.exit(1) | 
|  |  | 
|  | in_path = os.path.normpath(sys.argv[1]) | 
|  | out_path = os.path.normpath(sys.argv[2]) | 
|  |  | 
|  | if in_path == out_path: | 
|  | sys.stderr.write('ERROR: OUTPUT_FILE must be different from INPUT_FILE\n') | 
|  | sys.stderr.write(kUsage) | 
|  | sys.exit(1) | 
|  |  | 
|  | size_str = sys.argv[3] | 
|  | size_bytes = parse_filesize_str(size_str) | 
|  | if size_bytes is None: | 
|  | sys.stderr.write('ERROR: Could not parse TRUNCATED_SIZE: %s\n' % size_str) | 
|  | sys.stderr.write(kUsage) | 
|  | sys.exit(1) | 
|  |  | 
|  | truncate_log_file(in_path, out_path, size_bytes) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |