diff options
| author | Dino Oliva <dpo@google.com> | 2016-05-20 16:39:02 -0700 |
|---|---|---|
| committer | Dino Oliva <dpo@google.com> | 2016-05-20 16:39:02 -0700 |
| commit | 6712e0f623f659ee497b85be255671b961c2f647 (patch) | |
| tree | 5a5d8f72839645e8275135c0e76dd1fbbfaccdd3 /examples | |
| download | platform_external_opencensus-java-6712e0f623f659ee497b85be255671b961c2f647.tar.gz platform_external_opencensus-java-6712e0f623f659ee497b85be255671b961c2f647.tar.bz2 platform_external_opencensus-java-6712e0f623f659ee497b85be255671b961c2f647.zip | |
Initial import of Java Census.
Diffstat (limited to 'examples')
9 files changed, 1009 insertions, 0 deletions
diff --git a/examples/java/com/google/census/examples/BUILD b/examples/java/com/google/census/examples/BUILD new file mode 100644 index 00000000..d2988d3d --- /dev/null +++ b/examples/java/com/google/census/examples/BUILD @@ -0,0 +1,20 @@ +load("//third_party/java/grpc:build_defs.bzl", "grpc_java_library") + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # BSD + +exports_files(["LICENSE"]) + +proto_library( + name = "gol_proto", + srcs = ["gol.proto"], + has_services = 1, + java_api_version = 2, +) + +grpc_java_library( + name = "gol_proto_grpc", + src = "gol.proto", + proto_deps = [":gol_proto"], +) diff --git a/examples/java/com/google/census/examples/gol.proto b/examples/java/com/google/census/examples/gol.proto new file mode 100644 index 00000000..d861926a --- /dev/null +++ b/examples/java/com/google/census/examples/gol.proto @@ -0,0 +1,55 @@ +/* + * Copyright 2015, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// The definition of the example census service. + +syntax = "proto3"; + +package monitoring.census; +option java_multiple_files = true; +option java_package = "com.google.census.examples"; +option java_outer_classname = "GolProto"; + +// The example service definition. +service CommandProcessor { + // Executes the command. + rpc Execute(CommandRequest) returns (CommandResponse) { } +} + +// The request message containing the user's command. +message CommandRequest { + string req = 1; +} + +// The response message containing the user's command result. +message CommandResponse { + string retval = 1; +} diff --git a/examples/java/com/google/census/examples/gol/BUILD b/examples/java/com/google/census/examples/gol/BUILD new file mode 100644 index 00000000..2228f0a1 --- /dev/null +++ b/examples/java/com/google/census/examples/gol/BUILD @@ -0,0 +1,45 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # BSD + +exports_files(["LICENSE"]) + +java_binary( + name = "CensusClient", + srcs = [ + "CensusApplication.java", + "CensusClient.java", + "CensusClientz.java", + ], + main_class = "com.google.census.examples.gol.CensusClient", + deps = [ + "//java/com/google/common/base", + "//java/com/google/common/flags", + "//java/com/google/monitoring/runtime:hooks", + "//third_party/java/grpc:core", + "//third_party/java_src/census", + "//third_party/java_src/census:census_google3", + "//third_party/java_src/census:census_grpc", + "//third_party/java_src/census/examples/java/com/google/census/examples:gol_proto", + "//third_party/java_src/census/examples/java/com/google/census/examples:gol_proto_grpc", + ], +) + +java_binary( + name = "CensusServer", + srcs = [ + "CensusServer.java", + "GameOfLife.java", + ], + main_class = "com.google.census.examples.gol.CensusServer", + deps = [ + "//java/com/google/common/flags", + "//third_party/java/grpc:core", + "//third_party/java/grpc:stub", + "//third_party/java_src/census", + "//third_party/java_src/census:census_google3", + "//third_party/java_src/census:census_grpc", + "//third_party/java_src/census/examples/java/com/google/census/examples:gol_proto", + "//third_party/java_src/census/examples/java/com/google/census/examples:gol_proto_grpc", + ], +) diff --git a/examples/java/com/google/census/examples/gol/CensusApplication.java b/examples/java/com/google/census/examples/gol/CensusApplication.java new file mode 100644 index 00000000..d54f72dc --- /dev/null +++ b/examples/java/com/google/census/examples/gol/CensusApplication.java @@ -0,0 +1,160 @@ +/* + * Copyright 2016, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.census.examples.gol; + +import com.google.census.CensusScope; +import com.google.census.TagKey; +import com.google.census.TagMap; + +// Invokes the given CensusClient for all of the Game-of-Life specs with the appropriate +// Census client tag in scope. +class CensusApplication { + static final TagKey CLIENT_KEY = new TagKey("census_gol_client"); + + final CensusClient client; + final int gensPerRpc; + final int numRpcs; + + CensusApplication(CensusClient client, int gensPerRpc, int numRpcs) { + this.client = client; + this.gensPerRpc = gensPerRpc; + this.numRpcs = numRpcs; + } + + void execute() { + for (GolSpec gol : gols) { + TagMap tags = TagMap.of(CLIENT_KEY, getTagValue(gol.dim, gensPerRpc)); + String request = getRequest(gol.currentGen, gensPerRpc); + try (CensusScope scope = new CensusScope(tags)) { + for (int i = 0; i < numRpcs; ++i) { + String result = client.executeCommand(request); + String[] results = result.split("; "); + if (results.length < 1) { + break; + } + gol.currentGen = results[0]; + gol.gens += gensPerRpc; + } + } + } + } + + // Generates tag value as <dimension>x<dimension>-<generations>. + static String getTagValue(int dim, int gensPerRpc) { + return dim + "x" + dim + "-" + gensPerRpc; + } + + // Encodes a request for the given generation and number of generations to calculate. + static String getRequest(String gen, int gensPerRpc) { + return "gol " + gensPerRpc + " " + gen; + } + + // Holds the specification of an RPC for the game of life server. + static class GolSpec { + final int dim; + final String initGen; + String currentGen; + long gens; + + GolSpec(String initGen) { + this.initGen = initGen; + this.dim = (int) Math.sqrt(initGen.length()); + this.currentGen = initGen; + this.gens = 0; + } + } + + static final GolSpec[] gols = new GolSpec[] { + + new GolSpec("" + + "00000000" + + "00111000" + + "00000000" + + "00000000" + + "00010000" + + "00010000" + + "00010000" + + "00000000"), + + new GolSpec("" + + "0000000000000000" + + "0011100000111000" + + "0000000000000000" + + "0000000000000000" + + "0001000000010000" + + "0001000000010000" + + "0001000000010000" + + "0000000000000000" + + "0000000000000000" + + "0011100000111000" + + "0000000000000000" + + "0000000000000000" + + "0001000000010000" + + "0001000000010000" + + "0001000000010000" + + "0000000000000000"), + + new GolSpec("" + + "00000000000000000000000000000000" + + "00111000001110000011100000111000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00010000000100000001000000010000" + + "00010000000100000001000000010000" + + "00010000000100000001000000010000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00111000001110000011100000111000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00010000000100000001000000010000" + + "00010000000100000001000000010000" + + "00010000000100000001000000010000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00111000001110000011100000111000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00010000000100000001000000010000" + + "00010000000100000001000000010000" + + "00010000000100000001000000010000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00111000001110000011100000111000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00010000000100000001000000010000" + + "00010000000100000001000000010000" + + "00010000000100000001000000010000" + + "00000000000000000000000000000000"), + }; +} diff --git a/examples/java/com/google/census/examples/gol/CensusClient.java b/examples/java/com/google/census/examples/gol/CensusClient.java new file mode 100644 index 00000000..34981cd1 --- /dev/null +++ b/examples/java/com/google/census/examples/gol/CensusClient.java @@ -0,0 +1,126 @@ +/* + * Copyright 2016, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.census.examples.gol; + +import com.google.census.CensusClientInterceptor; +import com.google.census.examples.CommandProcessorGrpc; +import com.google.census.examples.CommandRequest; +import com.google.census.examples.CommandResponse; +import com.google.common.flags.Flag; +import com.google.common.flags.FlagSpec; +import com.google.common.flags.Flags; +import com.google.monitoring.runtime.HookManager; +import com.google.net.rpc3.server.RpcServer; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; + +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * A client for the Census Game-of-Life example. + */ +public class CensusClient { + private static final Logger logger = Logger.getLogger(CensusClient.class.getName()); + + private final ManagedChannel channel; + private final CommandProcessorGrpc.CommandProcessorBlockingStub blockingStub; + + @FlagSpec( + name = "port", + help = "The Game-of-Life client stubby port.") + private static final Flag<Integer> port = Flag.value(10001); + + @FlagSpec( + name = "gol_server_grpc_port", + help = "The Game-of-Life server gRPC port.") + private static final Flag<Integer> golServerGrpcPort = Flag.value(20000); + + @FlagSpec( + name = "gol_server_host", + help = "The Game-of-Life server host.") + private static final Flag<String> golServerHost = Flag.value("localhost"); + + @FlagSpec( + name = "gol_gens_per_rpc", + help = "The number of generations to calculate per rpc.") + private static final Flag<Integer> golGensPerRpc = Flag.value(1001); + + /** Construct client connecting to GameOfLife server at {@code host:port}. */ + public CensusClient(String host, int port) { + channel = ManagedChannelBuilder.forAddress(host, port) + .usePlaintext(true) + .intercept(new CensusClientInterceptor()) + .build(); + blockingStub = CommandProcessorGrpc.newBlockingStub(channel); + } + + public void shutdown() throws InterruptedException { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + } + + /** + * Invokes the synchronous RPC call that remotely executes the census command. + * Returns the result of executing the command. + * + * @param command the command string to send + * @return the result of executing the command + */ + public String executeCommand(String command) { + try { + CommandRequest request = CommandRequest.newBuilder().setReq(command).build(); + CommandResponse response = blockingStub.execute(request); + return response.getRetval(); + } catch (RuntimeException e) { + logger.log(Level.WARNING, "RPC failed", e); + return ""; + } + } + + /** Game of Life client. */ + public static void main(String[] args) throws Exception { + // TODO(dpo): this call is necessary to run the completion hook to enable CensusNative. In + // general, G3 services built with gRPC will need to do this otherwise many libries will not + // work correctly. This issue needs to be resolved with the gRPC team. + Flags.parse(args); + + HookManager.getManager().addHook( + new CensusClientz(golServerHost.get(), golServerGrpcPort.get(), golGensPerRpc.get())); + + // TODO(dpo): remove stubby-based server for monitoring hooks once gRPC team has implemented + // direct support. + RpcServer.newBuilder(port.get()).createAndStart().blockUntilShutdown(); + } +} diff --git a/examples/java/com/google/census/examples/gol/CensusClientz.java b/examples/java/com/google/census/examples/gol/CensusClientz.java new file mode 100644 index 00000000..7da1fc10 --- /dev/null +++ b/examples/java/com/google/census/examples/gol/CensusClientz.java @@ -0,0 +1,208 @@ +/* + * Copyright 2016, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.census.examples.gol; + +import com.google.monitoring.runtime.MonitoringHook; +import com.google.monitoring.runtime.RequestHandlerAdapter; + +import java.io.PrintWriter; +import java.util.Map; + +/** + * Censusz handler. Displays Census internal state. + * + * @author dpo@google.com (Dino Oliva) + */ +public class CensusClientz implements MonitoringHook { + private final String defaultHost; + private final int defaultPort; + private final int gensPerRpc; + + public CensusClientz(String host, int defaultPort, int gensPerRpc) { + this.defaultHost = host; + this.defaultPort = defaultPort; + this.gensPerRpc = gensPerRpc; + } + + @Override + public String getLinkName() { + return "censusclientz"; + } + + @Override + public String getLinkHtml(String linkUrl) { + return "Show <a href=\"" + linkUrl + "\">censusclientz</a>."; + } + + @Override + public void handleRequest(RequestHandlerAdapter handler) throws Exception { + // set params based on input + Map<String, String[]> params = handler.getParameterMap(); + String host = getParamValueOrDefault(params, "server", defaultHost); + int port = getIntOrDefault(getParamValue(params, "port"), defaultPort); + int numRpcs = getIntOrDefault(getParamValue(params, "rpcs"), 0); + CensusClient client = new CensusClient(host, port); + try { + // execute rpcs + new CensusApplication(client, gensPerRpc, numRpcs).execute(); + // display result + handler.setContentType("text/html"); + PrintWriter pw = handler.getPrintWriter(); + writePage(pw, host, port, numRpcs); + return; + } finally { + client.shutdown(); + } + } + + private void writePage(PrintWriter pw, String server, int port, int rpcs) { + pw.println("<html>"); + pw.println("<head>"); + pw.println("<title>CensusClientz</title>"); + pw.println("<style>"); + pw.println("label { display: inline-block; width: 5em; }"); + pw.println("td { font-weight:normal; text-align:center; color:#38761d; }"); + pw.println("th { font-weight:normal; text-align:center; color:#23238e; }"); + pw.println("</style>"); + pw.println("</head>"); + pw.println("<body bgcolor=#ffffff>"); + pw.println("<h1>Census Client</h1>"); + pw.println("<form method=\"get\" action=\"censusclientz\">"); + + pw.println("<fieldset>"); + pw.println("<legend>Census Client</legend>"); + + pw.println("<p>"); + pw.println("<label for=\"server\">Server:</label>"); + pw.println("<input type=\"text\" name=\"server\" id=\"server\"value=\"" + server + "\"/>"); + + pw.println("<p>"); + pw.println("<label for=\"port\">Port:</label>"); + pw.println("<input type=\"text\" name=\"port\" id=\"port\"value=\"" + port + "\"/>"); + + pw.println("<p>"); + pw.println("<label for=\"rpcs\">RPCs:</label>"); + pw.println("<select name=\"rpcs\">"); + pw.println("<option " + (rpcs == 16 ? "selected " : "") + "value=16>16</option>"); + pw.println("<option " + (rpcs == 128 ? "selected " : "") + "value=128>128</option>"); + pw.println("<option " + (rpcs == 256 ? "selected " : "") + "value=256>256</option>"); + pw.println("</select>"); + pw.println("</fieldset>"); + + pw.println("<p><input type=\"submit\"/></p>"); + + for (CensusApplication.GolSpec gol : CensusApplication.gols) { + pw.println("<p>"); + pw.println("<fieldset>"); + pw.println("<legend>RPC " + getTitle(gol.dim, gensPerRpc) + "</legend>"); + pw.println("<ul>"); + pw.println("<li><strong>Tag:</strong> " + + CensusApplication.CLIENT_KEY + ":x" + + CensusApplication.getTagValue(gol.dim, gensPerRpc)); + pw.println("<li><strong>Dimensions:</strong> " + gol.dim + "x" + gol.dim); + pw.println("<li><strong>Generations per RPC:</strong> " + gensPerRpc); + pw.println("<li><strong>Result:</strong> "); + pw.println(formatResultAsTable(gol.initGen, gol.currentGen, gol.dim, gol.gens)); + pw.println("</ul>"); + pw.println("</fieldset>"); + } + pw.println("</form>"); + pw.println("</body>"); + pw.println("</html>"); + } + + private static String getTitle(int dim, int gensPerRpc) { + return dim + "x" + dim + " Board/" + gensPerRpc + " Generations Per RPC"; + } + + private static String formatResultAsTable(String initGen, String currGen, int dim, long gens) { + StringBuilder retval = new StringBuilder(); + retval.append("<table>"); + int initIndex = 0; + int currIndex = 0; + retval.append("<tr>"); + retval.append("<td style=\"font-weight:bold;\" colspan=\"") + .append(dim + 1).append("\">InitGen</td>"); + retval.append("<th style=\"font-weight:bold;\" colspan=\"") + .append(dim).append("\">Gen ").append(gens).append("</th>"); + retval.append("</tr>"); + for (int row = 0; row < dim; ++row) { + retval.append("<tr>"); + for (int col = 0; col < dim; ++col) { + retval.append("<td>"); + if (initGen.charAt(initIndex) == '1') { + retval.append("*"); + } else { + retval.append(" "); + } + retval.append("</td>"); + ++initIndex; + } + retval.append("<td>   </td>"); + for (int col = 0; col < dim; ++col) { + retval.append("<th>"); + if (currGen.charAt(currIndex) == '1') { + retval.append("*"); + } else { + retval.append(" "); + } + retval.append("</th>"); + ++currIndex; + } + retval.append("</tr>"); + } + retval.append("</table>"); + return retval.toString(); + } + + private static String getParamValue(Map<String, String[]> params, String name) { + String[] values = params.get(name); + if (values == null || values.length == 0) { + return null; + } + return values[values.length - 1]; + } + + private static String getParamValueOrDefault( + Map<String, String[]> params, String name, String defaultValue) { + String val = getParamValue(params, name); + return (val == null) ? defaultValue : val; + } + + private static int getIntOrDefault(String s, int dflt) { + try { + return Integer.parseInt(s); + } catch (NumberFormatException exn) { + return dflt; + } + } +} diff --git a/examples/java/com/google/census/examples/gol/CensusServer.java b/examples/java/com/google/census/examples/gol/CensusServer.java new file mode 100644 index 00000000..875562ef --- /dev/null +++ b/examples/java/com/google/census/examples/gol/CensusServer.java @@ -0,0 +1,168 @@ +/* + * Copyright 2016, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.census.examples.gol; + +import com.google.census.CensusScope; +import com.google.census.CensusServerInterceptor; +import com.google.census.TagKey; +import com.google.census.TagMap; +import com.google.census.examples.CommandProcessorGrpc; +import com.google.census.examples.CommandRequest; +import com.google.census.examples.CommandResponse; +import com.google.common.flags.Flag; +import com.google.common.flags.FlagSpec; +import com.google.common.flags.Flags; +import com.google.net.rpc3.server.RpcServer; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerInterceptors; +import io.grpc.stub.StreamObserver; + +import java.util.logging.Logger; + +/** + * Census Game of Life Server. + * <p> + * Requests from the client are expected to be Strings of the form: + * <p> + * {@code gol <integer number of generations> <string encoding of initial generation>} + * <p> + * The server parses the input then uses the GameOfLife class to return + * a string encoding of the initial generation after the specified + * number of generations. + * + * @author dpo@google.com (Dino Oliva) + */ +public class CensusServer { + private static final Logger logger = Logger.getLogger(CensusServer.class.getName()); + private static final TagKey SERVER_KEY = new TagKey("census_gol_server"); + + private final Server server; + + @FlagSpec( + name = "port", + help = "The Game-of-Life server stubby port.") + private static final Flag<Integer> port = Flag.value(10000); + + /* The port on which the server should run */ + @FlagSpec( + name = "gol_server_grpc_port", + help = "The Game-of-Life server grpc port.") + private static final Flag<Integer> golServerGrpcPort = Flag.value(20000); + + CensusServer() throws Exception { + server = ServerBuilder.forPort(golServerGrpcPort.get()) + .addService(ServerInterceptors.intercept(CommandProcessorGrpc.bindService( + new CommandProcessorImpl()), new CensusServerInterceptor())) + .build() + .start(); + logger.info("Server started, listening on " + golServerGrpcPort.get()); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + CensusServer.this.stop(); + System.err.println("*** server shut down"); + } + }); + } + + private void stop() { + if (server != null) { + server.shutdown(); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + public static void main(String[] args) throws Exception { + // TODO(dpo): this call is necessary to run the completion hook to enable CensusNative. In + // general, G3 services built with gRPC will need to do this otherwise many libries will not + // work correctly. This issue needs to be resolved with the gRPC team. + Flags.parse(args); + + // TODO(dpo): remove stubby-based server for monitoring hooks once gRPC team has implemented + // direct support. + RpcServer.newBuilder(port.get()).createAndStart(); + logger.info("Server started, monitoring hooks on " + port.get()); + CensusServer server = new CensusServer(); + server.blockUntilShutdown(); + } + + private class CommandProcessorImpl implements CommandProcessorGrpc.CommandProcessor { + @Override + public void execute(CommandRequest request, StreamObserver<CommandResponse> responseObserver) { + String retval = executeCommand(request.getReq()); + CommandResponse response = CommandResponse.newBuilder().setRetval(retval).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + } + + private static String executeCommand(String command) { + String[] decode = command.split(" "); + if (decode.length == 3 && decode[0].equals("gol")) { + int gens = toIntWithDefault(decode[1], 0); + int dims = (int) Math.sqrt(decode[2].length()); + if (dims * dims == decode[2].length()) { + // add server tag + TagMap tags = TagMap.of(SERVER_KEY, genTagValue(dims, gens)); + try (CensusScope scope = new CensusScope(tags)) { + return new GameOfLife(dims, decode[2]).calcNextGenerations(gens).encode(); + } + } + } + return "Error: bad request"; + } + + private static String genTagValue(int dim, int gens) { + return dim + "x" + dim + "-" + gens; + } + + private static int toIntWithDefault(String s, int i) { + try { + return Integer.valueOf(s); + } catch (NumberFormatException exn) { + return i; + } + } + +} diff --git a/examples/java/com/google/census/examples/gol/GameOfLife.java b/examples/java/com/google/census/examples/gol/GameOfLife.java new file mode 100644 index 00000000..6d34ce92 --- /dev/null +++ b/examples/java/com/google/census/examples/gol/GameOfLife.java @@ -0,0 +1,176 @@ +/* + * Copyright 2016, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.census.examples.gol; + +/** + * This class implements Conway's Game of Life. The Game of Life consists of + * a matrix of cells that are either alive or dead. The next generation is + * calculated from the current generation based on the following rules: + * <ol> + * <li> If a cell is alive in the current generation, it will be alive in the + * next generation iff it has less than 2 live neighbors or more than 3 + * live neighbors in the current generation. + * <li> If a cell is dead in the current generation, it will be alive in the + * next generation iff it has exactly 3 live neighbors in the current + * generation. + * </ol> + * This implementation is restricted to square matricies. + * <p> + * @author dpo@google.com (Dino Oliva) + */ +public class GameOfLife { + // Square matrix representation of the current generation - a live cell is + // represented by 'true' and a dead cell by 'false'. + private final boolean[][] generation; + + // Dimension of the square matrix. + final int dimension; + + /** + * Creates an instance of the GameOfLife with the current generation + * initalized to the input layout String. The layout is assumed to be in + * row-major order with character '0' representing false and any other + * character (typically '1') representing true. + * <p> + * Note that if the length of the input is not a square, the dimensions + * of the generation is set to 0. + * + * @param layout Specification of the initial generation. + * @return instance with the current generation initialized as specified. + */ + public GameOfLife(int dimension, String layout) { + this.dimension = dimension; + generation = new boolean[dimension][dimension]; + int index = 0; + for (int row = 0; row < dimension; ++row) { + for (int col = 0; col < dimension; ++col) { + generation[row][col] = toBool(layout.charAt(index)); + ++index; + } + } + } + + // Creates an instance of the GameOfLife with the specified layout and dimension. + // Assumes that 'generation' is a square matrix of dimension 'dimension'. + private GameOfLife(boolean[][] generation, int dimension) { + this.dimension = dimension; + this.generation = generation; + } + + /** + * Encodes the current generation as a string in row-major order with + * character '1' to representing true and '0' to representing false. + * + * @return String encoding of the current generation. + */ + public String encode() { + StringBuilder result = new StringBuilder(dimension * dimension); + for (int row = 0; row < dimension; ++row) { + for (int col = 0; col < dimension; ++col) { + result.append(generation[row][col] ? "1" : "0"); + } + } + return result.toString(); + } + + /** + * Updates current generation the input number of times. + * + * @param gens the number of generations to calculate. + */ + public GameOfLife calcNextGenerations(int gens) { + GameOfLife result = this; + for (int i = 0; i < gens; ++i) { + result = result.calcNextGeneration(); + } + return result; + } + + // Calculates the next generation based on the current generation then updates + // the current generation. + private GameOfLife calcNextGeneration() { + boolean[][] nextGeneration = new boolean[dimension][dimension]; + // Calculates the value of each cell in the next generation based + // on it's direct neighbors in the current generation. + for (int row = 0; row < dimension; ++row) { + for (int col = 0; col < dimension; ++col) { + nextGeneration[row][col] = calcCell(row, col); + } + } + return new GameOfLife(nextGeneration, dimension); + } + + // Calculates whether the cell specified by the input row and column is dead + // or alive in the next generation based on it's neighbors in the current + // generation. The rules are: + // + // 1. If the cell is alive in the current generation, it will be alive in the + // next generation iff it has less than 2 live neighbors or more than 3 + // live neighbors in the current generation. + // + // 2. If the cell is dead in the current generation, it will be alive in the + // next generation iff it has exactly 3 live neighbors in the current + // generation. + // + // Note that this calculation uses wrap-around when counting neighbors such + // that the bottom row uses the top row (and vice-versa) and the left-most + // column use the right-most column (and vice-versa) as neighbors. + private boolean calcCell(int row, int col) { + int liveNeighbors = 0; + // This loop would range from -1 to 1 except that Java's modulo operator + // returns a value with the same sign as the numerator, so 'dimension' is + // added to ensure that the numerator is always positive. + for (int rowX = dimension - 1; rowX < dimension + 2; ++rowX) { + for (int colX = dimension - 1; colX < dimension + 2; ++colX) { + if (generation[(row + rowX) % dimension][(col + colX) % dimension]) { + liveNeighbors++; + } + } + } + if (generation[row][col]) { + liveNeighbors--; + if (liveNeighbors < 2 || liveNeighbors > 3) { + return false; + } + return true; + } else { + if (liveNeighbors == 3) { + return true; + } + return false; + } + } + + private static boolean toBool(char c) { + return c != '0'; + } +} diff --git a/examples/java/com/google/census/examples/gol/census_gol.borg b/examples/java/com/google/census/examples/gol/census_gol.borg new file mode 100644 index 00000000..86e6c762 --- /dev/null +++ b/examples/java/com/google/census/examples/gol/census_gol.borg @@ -0,0 +1,51 @@ +import "//production/borg/templates/java/java.borg" as java +import "//production/borg/templates/common.borg" as common + +vars { + cell = 'wk' // Borg cell to run client and server jobs in. +} + +service census-gol = { + params { + cell = vars.cell + google3 = replace(getcwd(), 'google3.*', 'google3') + binary_base_dir = "%google3%/blaze-bin/third_party/java_src/census/examples/java/com/google/census/examples/gol" // NOLINT + } + + template job census_gol_job = java.java_job { + args { + port = '%port%' + census_cpu_accounting_enabled = true + census_enabled = true + census_whitelist_regexp_for_resourcez_services = '.*' + rpc_census_record_stats = true + } + + appclass { + type = 'BEST_EFFORT' + } + + binary = "%params.binary_base_dir%/%gol_binary_name%" + + requirements { + ram = 2G + disk = 500M + cpu = 1 + } + + runtime { + cell = params.cell + } + } + + job client = census_gol_job { + gol_binary_name = 'CensusClient_deploy.jar' + } + + job server = census_gol_job { + gol_binary_name = 'CensusServer_deploy.jar' + args { + gol_server_grpc_port = '%port_grpc%' + } + } +} |
