Skip to content
Permalink
Browse files
bot: add profile handler
Reviewed-by: erikj
  • Loading branch information
edvbld committed May 28, 2021
1 parent 00ada5d commit 92bf2dcc446fdc1c1c3808e40a49123e0f1e8f94
@@ -34,6 +34,7 @@
requires java.management;
requires jdk.management;
requires jdk.httpserver;
requires jdk.jfr;

exports org.openjdk.skara.bot;

@@ -407,7 +407,8 @@ Optional<HttpServerConfiguration> httpServer(BotRunner runner) {
WebhookHandler.name(), WebhookHandler::create,
MetricsHandler.name(), MetricsHandler::create,
ReadinessHandler.name(), ReadinessHandler::create,
LivenessHandler.name(), LivenessHandler::create
LivenessHandler.name(), LivenessHandler::create,
ProfileHandler.name(), ProfileHandler::create
);
var contexts = new ArrayList<HttpContextConfiguration>();
var port = config.get("http-server").get("port").asInt();
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.skara.bot;

import org.openjdk.skara.json.JSONObject;

import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.logging.*;
import com.sun.net.httpserver.*;
import jdk.jfr.*;
import java.text.ParseException;
import java.util.concurrent.locks.ReentrantLock;

class ProfileHandler implements HttpHandler {
private static final Logger log = Logger.getLogger("org.openjdk.skara.bot");
private final Path configurationPath;
private final int maxDuration;
private final String token;
private final ReentrantLock lock = new ReentrantLock();

private ProfileHandler(Path configurationPath, int maxDuration, String token) {
this.configurationPath = configurationPath;
this.maxDuration = maxDuration;
this.token = token;
}

private static Map<String, String> parameters(HttpExchange exchange) {
var query = exchange.getRequestURI().getQuery();
var parts = query.split("&");
var result = new HashMap<String, String>();
for (var part : parts) {
var keyAndValue = part.split("=");
result.put(keyAndValue[0], keyAndValue[1]);
}
return result;
}

private void handleLocked(HttpExchange exchange) throws IOException {
var params = parameters(exchange);
var seconds = params.getOrDefault("seconds", "30");
var configurationName = params.getOrDefault("configuration", "profile");

Configuration configuration = null;
try {
configuration = Configuration.create(configurationPath);
} catch (ParseException e) {
log.log(Level.WARNING, "Could not get JFR configuration", e);
exchange.sendResponseHeaders(500, 0);
exchange.getResponseBody().close();
}

log.info("Profiling for " + seconds + " seconds with configuration " + configurationName);
var recording = new Recording(configuration);
recording.start();

try {
var duration = Integer.parseInt(seconds);
if (duration > maxDuration) {
duration = maxDuration;
}
Thread.sleep(duration * 1000);
} catch (InterruptedException e) {
log.log(Level.WARNING, "Thread interrupted when sleeping", e);
exchange.sendResponseHeaders(500, 0);
exchange.getResponseBody().close();
}

recording.stop();
var path = Files.createTempFile("recording", "jfr");
recording.dump(path);

var buffer = new byte[4096];
exchange.sendResponseHeaders(200, Files.size(path));
try (var output = exchange.getResponseBody(); var stream = Files.newInputStream(path)) {
while (true) {
var read = stream.read(buffer);
if (read == -1) {
break;
}
output.write(buffer, 0, read);
}
} catch (Throwable t) {
log.log(Level.WARNING, "Could not send JFR recording", t);
} finally {
Files.deleteIfExists(path);
}
}

@Override
public void handle(HttpExchange exchange) throws IOException {
var authHeader = exchange.getRequestHeaders().getFirst("Authorization");
if (authHeader == null) {
log.log(Level.WARNING, "Authorization HTTP header missing");
exchange.sendResponseHeaders(401, 0);
exchange.getResponseBody().close();
return;
}
var authParts = authHeader.split(" ");
if (authParts.length != 2 || !authParts[0].equals("token")) {
log.log(Level.WARNING, "Authorization HTTP header has wrong format");
exchange.sendResponseHeaders(401, 0);
exchange.getResponseBody().close();
return;
}
if (!authParts[1].equals(token)) {
log.log(Level.WARNING, "Wrong authorization token: " + authParts[1]);
exchange.sendResponseHeaders(401, 0);
exchange.getResponseBody().close();
return;
}

// Only allow one recording at a time.
lock.lock();
try {
handleLocked(exchange);
} finally {
lock.unlock();
}
}

static ProfileHandler create(BotRunner runner, JSONObject configuration) {
var configurationPath = Path.of(configuration.get("configuration").asString());
var maxDuration = configuration.get("max-duration").asInt();
var token = configuration.get("token").asString();
return new ProfileHandler(configurationPath, maxDuration, token);
}

static String name() {
return "profile";
}
}

1 comment on commit 92bf2dc

@openjdk-notifier
Copy link

@openjdk-notifier openjdk-notifier bot commented on 92bf2dc May 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.