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