// Copyright 2016 Twitter. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.twitter.heron.metricscachemgr; import java.io.IOException; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.util.Arrays; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import com.google.protobuf.GeneratedMessage; import com.google.protobuf.InvalidProtocolBufferException; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import com.twitter.heron.metricscachemgr.metricscache.MetricsCache; import com.twitter.heron.proto.tmaster.TopologyMaster; import com.twitter.heron.spi.common.Config; import com.twitter.heron.spi.common.Key; import com.twitter.heron.spi.utils.NetworkUtils; import com.twitter.heron.statemgr.localfs.LocalFileSystemStateManager; /** * MetricsCacheMgr http server: * compatible with tmaster and tracker http interface for metrics * http path: * "/stats" metric query * "/exceptions" exception query * "/exceptionsummary" exception query, with aggregation * <p> * Differece from MetricsCacheManagerServer: * 1. MetricsCacheManagerServer accepts metric publishing message from sinks; * MetricsCacheManagerHttpServer responds to queries. * 2. MetricsCacheManagerServer is a HeronServer; * MetricsCacheManagerHttpServer is a http server */ public class MetricsCacheManagerHttpServer { // http path, compatible with tmaster stat interface private static final String PATH_STATS = "/stats"; private static final String PATH_EXCEPTIONS = "/exceptions"; private static final String PATH_EXCEPTIONSUMMARY = "/exceptionsummary"; private static final Logger LOG = Logger.getLogger(MetricsCacheManagerHttpServer.class.getName()); // http server private final HttpServer server; // reference to MetricsCache object private final MetricsCache metricsCache; public MetricsCacheManagerHttpServer(MetricsCache cache, int port) throws IOException { metricsCache = cache; server = HttpServer.create(new InetSocketAddress(port), 0); server.createContext(PATH_STATS, new HandleStatsRequest()); server.createContext(PATH_EXCEPTIONS, new HandleExceptionRequest()); server.createContext(PATH_EXCEPTIONSUMMARY, new HandleExceptionSummaryRequest()); } /** * manual test for local mode topology, for debug * How to run: * in the [source root directory], run bazel test, * bazel run heron/metricscachemgr/src/java:metricscache-queryclient-unshaded -- \ * <topology_name> <component_name> <metrics_name> * Example: * 1. run the example topology, * ~/bin/heron submit local ~/.heron/examples/heron-examples.jar \ * com.twitter.heron.examples.ExclamationTopology ExclamationTopology \ * --deploy-deactivated --verbose * 2. in the [source root directory], * bazel run heron/metricscachemgr/src/java:metricscache-queryclient-unshaded -- \ * ExclamationTopology exclaim1 \ * __emit-count __execute-count __fail-count __ack-count __complete-latency __execute-latency \ * __process-latency __jvm-uptime-secs __jvm-process-cpu-load __jvm-memory-used-mb \ * __jvm-memory-mb-total __jvm-gc-collection-time-ms __server/__time_spent_back_pressure_initiated \ * __time_spent_back_pressure_by_compid */ public static void main(String[] args) throws ExecutionException, InterruptedException, IOException { if (args.length < 3) { System.out.println( "Usage: java MetricsQuery <topology_name> <component_name> <metrics_name>"); } else { System.out.println("topology: " + args[0] + "; component: " + args[1]); } Config config = Config.newBuilder() .put(Key.STATEMGR_ROOT_PATH, System.getProperty("user.home") + "/.herondata/repository/state/local") .build(); LocalFileSystemStateManager stateManager = new LocalFileSystemStateManager(); stateManager.initialize(config); TopologyMaster.MetricsCacheLocation location = stateManager.getMetricsCacheLocation(null, args[0]).get(); if (location == null || !location.isInitialized()) { System.out.println("MetricsCacheMgr is not ready"); return; } // construct metric cache stat url String url = "http://" + location.getHost() + ":" + location.getStatsPort() + MetricsCacheManagerHttpServer.PATH_STATS; // construct query payload byte[] requestData = TopologyMaster.MetricRequest.newBuilder() .setComponentName(args[1]) .setMinutely(true) .setInterval(-1) .addAllMetric(Arrays.asList(Arrays.copyOfRange(args, 2, args.length))) .build().toByteArray(); // http communication HttpURLConnection con = NetworkUtils.getHttpConnection(url); NetworkUtils.sendHttpPostRequest(con, "X", requestData); byte[] responseData = NetworkUtils.readHttpResponse(con); // parse response data TopologyMaster.MetricResponse response = TopologyMaster.MetricResponse.parseFrom(responseData); System.out.println(response.toString()); } public void start() { server.start(); } public void stop() { server.stop(0); } // T - request, U - response abstract class RequestHandler<T extends GeneratedMessage, U extends GeneratedMessage> implements HttpHandler { @Override public void handle(HttpExchange httpExchange) throws IOException { // get the entire stuff byte[] payload = NetworkUtils.readHttpRequestBody(httpExchange); T req; try { req = parseRequest(payload); } catch (InvalidProtocolBufferException e) { LOG.log(Level.SEVERE, "Unable to decipher data specified in Request: " + httpExchange, e); httpExchange.sendResponseHeaders(400, -1); // throw exception return; } U res = generateResponse(req, metricsCache); NetworkUtils.sendHttpResponse(httpExchange, res.toByteArray()); httpExchange.close(); } abstract T parseRequest(byte[] requestBytes) throws InvalidProtocolBufferException; abstract U generateResponse(T request, MetricsCache metricsCache1); } // compatible with tmaster stat interface: http+protobuf class HandleStatsRequest extends RequestHandler<TopologyMaster.MetricRequest, TopologyMaster.MetricResponse> { @Override public TopologyMaster.MetricRequest parseRequest(byte[] requestBytes) throws InvalidProtocolBufferException { return TopologyMaster.MetricRequest.parseFrom(requestBytes); } @Override public TopologyMaster.MetricResponse generateResponse( TopologyMaster.MetricRequest request, MetricsCache metricsCache1) { return metricsCache1.getMetrics(request); } } // compatible with tmaster exceptions interface: http+protobuf public class HandleExceptionRequest extends RequestHandler<TopologyMaster.ExceptionLogRequest, TopologyMaster.ExceptionLogResponse> { @Override public TopologyMaster.ExceptionLogRequest parseRequest(byte[] requestBytes) throws InvalidProtocolBufferException { return TopologyMaster.ExceptionLogRequest.parseFrom(requestBytes); } @Override public TopologyMaster.ExceptionLogResponse generateResponse( TopologyMaster.ExceptionLogRequest request, MetricsCache metricsCache1) { return metricsCache1.getExceptions(request); } } // compatible with tmaster exceptionsummary interface: http+protobuf public class HandleExceptionSummaryRequest extends RequestHandler<TopologyMaster.ExceptionLogRequest, TopologyMaster.ExceptionLogResponse> { @Override public TopologyMaster.ExceptionLogRequest parseRequest(byte[] requestBytes) throws InvalidProtocolBufferException { return TopologyMaster.ExceptionLogRequest.parseFrom(requestBytes); } @Override public TopologyMaster.ExceptionLogResponse generateResponse( TopologyMaster.ExceptionLogRequest request, MetricsCache metricsCache1) { return metricsCache1.getExceptionsSummary(request); } } // compatible with tracker: http+json // TODO(huijun) add compatible query interface for tracker public class MetricsHandler implements HttpHandler { @Override public void handle(HttpExchange httpExchange) throws IOException { httpExchange.close(); } } }