1a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// Protocol Buffers - Google's data interchange format 2a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// Copyright 2009 Google Inc. All rights reserved. 3afb4b72037e3f13db208590fc782c4bc8e27f862Jeff Davidson// https://developers.google.com/protocol-buffers/ 4a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// 5a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// Redistribution and use in source and binary forms, with or without 6a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// modification, are permitted provided that the following conditions are 7a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// met: 8a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// 9a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// * Redistributions of source code must retain the above copyright 10a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// notice, this list of conditions and the following disclaimer. 11a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// * Redistributions in binary form must reproduce the above 12a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// copyright notice, this list of conditions and the following disclaimer 13a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// in the documentation and/or other materials provided with the 14a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// distribution. 15a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// * Neither the name of Google Inc. nor the names of its 16a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// contributors may be used to endorse or promote products derived from 17a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// this software without specific prior written permission. 18a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// 19a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 31a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonpackage com.google.protocolbuffers; 32a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 33a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport java.io.ByteArrayInputStream; 34a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport java.io.ByteArrayOutputStream; 35a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport java.io.File; 36a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport java.io.FileOutputStream; 37a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport java.io.FileNotFoundException; 38a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport java.io.IOException; 39a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport java.io.RandomAccessFile; 40a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport java.lang.reflect.Method; 41a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 42a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport com.google.protobuf.ByteString; 43a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport com.google.protobuf.CodedInputStream; 44a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport com.google.protobuf.CodedOutputStream; 45a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonimport com.google.protobuf.Message; 46a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 47a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidsonpublic class ProtoBench { 48a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 49a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson private static final long MIN_SAMPLE_TIME_MS = 2 * 1000; 50a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson private static final long TARGET_TIME_MS = 30 * 1000; 51a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 52a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson private ProtoBench() { 53a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson // Prevent instantiation 54a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 55a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 56a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public static void main(String[] args) { 57a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson if (args.length < 2 || (args.length % 2) != 0) { 58a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.err.println("Usage: ProtoBench <descriptor type name> <input data>"); 59a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.err.println("The descriptor type name is the fully-qualified message name,"); 60a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.err.println("e.g. com.google.protocolbuffers.benchmark.Message1"); 61a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.err.println("(You can specify multiple pairs of descriptor type name and input data.)"); 62a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.exit(1); 63a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 64a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson boolean success = true; 65a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson for (int i = 0; i < args.length; i += 2) { 66a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson success &= runTest(args[i], args[i + 1]); 67a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 68a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.exit(success ? 0 : 1); 69a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 70a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 71a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson /** 72a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson * Runs a single test. Error messages are displayed to stderr, and the return value 73a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson * indicates general success/failure. 74a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson */ 75a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public static boolean runTest(String type, String file) { 76a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.out.println("Benchmarking " + type + " with file " + file); 77a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson final Message defaultMessage; 78a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson try { 79a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson Class<?> clazz = Class.forName(type); 80a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson Method method = clazz.getDeclaredMethod("getDefaultInstance"); 81a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson defaultMessage = (Message) method.invoke(null); 82a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } catch (Exception e) { 83a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson // We want to do the same thing with all exceptions. Not generally nice, 84a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson // but this is slightly different. 85a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.err.println("Unable to get default message for " + type); 86a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson return false; 87a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 88a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 89a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson try { 90a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson final byte[] inputData = readAllBytes(file); 91a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson final ByteArrayInputStream inputStream = new ByteArrayInputStream(inputData); 92a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson final ByteString inputString = ByteString.copyFrom(inputData); 93a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson final Message sampleMessage = defaultMessage.newBuilderForType().mergeFrom(inputString).build(); 94a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson FileOutputStream devNullTemp = null; 95a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson CodedOutputStream reuseDevNullTemp = null; 96a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson try { 97a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson devNullTemp = new FileOutputStream("/dev/null"); 98a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson reuseDevNullTemp = CodedOutputStream.newInstance(devNullTemp); 99a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } catch (FileNotFoundException e) { 100a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson // ignore: this is probably Windows, where /dev/null does not exist 101a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 102a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson final FileOutputStream devNull = devNullTemp; 103a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson final CodedOutputStream reuseDevNull = reuseDevNullTemp; 104a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson benchmark("Serialize to byte string", inputData.length, new Action() { 105a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public void execute() { sampleMessage.toByteString(); } 106a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson }); 107a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson benchmark("Serialize to byte array", inputData.length, new Action() { 108a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public void execute() { sampleMessage.toByteArray(); } 109a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson }); 110a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson benchmark("Serialize to memory stream", inputData.length, new Action() { 111a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public void execute() throws IOException { 112a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson sampleMessage.writeTo(new ByteArrayOutputStream()); 113a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 114a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson }); 115a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson if (devNull != null) { 116a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson benchmark("Serialize to /dev/null with FileOutputStream", inputData.length, new Action() { 117a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public void execute() throws IOException { 118a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson sampleMessage.writeTo(devNull); 119a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 120a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson }); 121a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson benchmark("Serialize to /dev/null reusing FileOutputStream", inputData.length, new Action() { 122a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public void execute() throws IOException { 123a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson sampleMessage.writeTo(reuseDevNull); 124a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson reuseDevNull.flush(); // force the write to the OutputStream 125a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 126a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson }); 127a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 128a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson benchmark("Deserialize from byte string", inputData.length, new Action() { 129a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public void execute() throws IOException { 130a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson defaultMessage.newBuilderForType().mergeFrom(inputString).build(); 131a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 132a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson }); 133a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson benchmark("Deserialize from byte array", inputData.length, new Action() { 134a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public void execute() throws IOException { 135a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson defaultMessage.newBuilderForType() 136a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson .mergeFrom(CodedInputStream.newInstance(inputData)).build(); 137a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 138a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson }); 139a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson benchmark("Deserialize from memory stream", inputData.length, new Action() { 140a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson public void execute() throws IOException { 141a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson defaultMessage.newBuilderForType() 142a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson .mergeFrom(CodedInputStream.newInstance(inputStream)).build(); 143a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson inputStream.reset(); 144a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 145a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson }); 146a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.out.println(); 147a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson return true; 148a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } catch (Exception e) { 149a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.err.println("Error: " + e.getMessage()); 150a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.err.println("Detailed exception information:"); 151a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson e.printStackTrace(System.err); 152a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson return false; 153a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 154a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 155a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 156a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson private static void benchmark(String name, long dataSize, Action action) throws IOException { 157a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson // Make sure it's JITted "reasonably" hard before running the first progress test 158a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson for (int i=0; i < 100; i++) { 159a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson action.execute(); 160a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 161a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 162a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson // Run it progressively more times until we've got a reasonable sample 163a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson int iterations = 1; 164a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson long elapsed = timeAction(action, iterations); 165a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson while (elapsed < MIN_SAMPLE_TIME_MS) { 166a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson iterations *= 2; 167a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson elapsed = timeAction(action, iterations); 168a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 169a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 170a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson // Upscale the sample to the target time. Do this in floating point arithmetic 171a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson // to avoid overflow issues. 172a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson iterations = (int) ((TARGET_TIME_MS / (double) elapsed) * iterations); 173a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson elapsed = timeAction(action, iterations); 174a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.out.println(name + ": " + iterations + " iterations in " 175a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson + (elapsed/1000f) + "s; " 176a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson + (iterations * dataSize) / (elapsed * 1024 * 1024 / 1000f) 177a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson + "MB/s"); 178a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 179a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 180a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson private static long timeAction(Action action, int iterations) throws IOException { 181a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson System.gc(); 182a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson long start = System.currentTimeMillis(); 183a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson for (int i = 0; i < iterations; i++) { 184a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson action.execute(); 185a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 186a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson long end = System.currentTimeMillis(); 187a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson return end - start; 188a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 189a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 190a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson private static byte[] readAllBytes(String filename) throws IOException { 191a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson RandomAccessFile file = new RandomAccessFile(new File(filename), "r"); 192a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson byte[] content = new byte[(int) file.length()]; 193a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson file.readFully(content); 194a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson return content; 195a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 196a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson 197a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson /** 198a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson * Interface used to capture a single action to benchmark. 199a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson */ 200a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson interface Action { 201a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson void execute() throws IOException; 202a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson } 203a3b2a6da25a76f17c73d31def3952feb0fd2296eJeff Davidson} 204