| /******************************************************************************** |
| * Copyright (c) 2020 Contributors to the Eclipse Foundation |
| * |
| * See the NOTICE file(s) distributed with this work for additional |
| * information regarding copyright ownership. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0 |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| ********************************************************************************/ |
| package org.eclipse.jifa.worker.route; |
| |
| import com.google.gson.reflect.TypeToken; |
| import io.vertx.core.buffer.Buffer; |
| import io.vertx.core.http.HttpMethod; |
| import io.vertx.ext.unit.Async; |
| import io.vertx.ext.unit.TestContext; |
| import io.vertx.ext.web.client.HttpRequest; |
| import io.vertx.ext.web.client.HttpResponse; |
| import org.eclipse.jifa.common.enums.ProgressState; |
| import org.eclipse.jifa.common.vo.PageView; |
| import org.eclipse.jifa.common.vo.Progress; |
| import org.eclipse.jifa.common.vo.Result; |
| import org.eclipse.jifa.worker.Global; |
| import org.eclipse.jifa.worker.vo.heapdump.classloader.Record; |
| import org.eclipse.jifa.worker.vo.heapdump.inspector.ObjectView; |
| import org.eclipse.jifa.worker.vo.heapdump.thread.Info; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.lang.reflect.Type; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import static org.eclipse.jifa.common.Constant.HTTP_GET_OK_STATUS_CODE; |
| import static org.eclipse.jifa.common.Constant.HTTP_POST_CREATED_STATUS_CODE; |
| import static org.eclipse.jifa.common.util.GsonHolder.GSON; |
| |
| public class HeapDumpRouteSuite extends Base { |
| |
| static TestContext context; |
| private static Logger LOGGER = LoggerFactory.getLogger(HeapDumpRouteSuite.class); |
| |
| public static void test(TestContext c) throws Exception { |
| context = c; |
| Holder holder = new Holder(); |
| |
| testGet("/isFirstAnalysis", |
| (PostProcessor) resp -> { |
| Type type = new TypeToken<Result<Boolean>>() { |
| }.getType(); |
| Result<Boolean> result = GSON.fromJson(resp.bodyAsString(), type); |
| context.assertTrue(result.getResult()); |
| |
| }); |
| |
| testPost("/analyze"); |
| |
| AtomicBoolean success = new AtomicBoolean(); |
| while (!success.get()) { |
| testGet("/progressOfAnalysis", |
| (PostProcessor) resp -> { |
| Progress progress = GSON.fromJson(resp.bodyAsString(), Progress.class); |
| ProgressState state = progress.getState(); |
| context.assertTrue(state == ProgressState.IN_PROGRESS || state == ProgressState.SUCCESS); |
| if (state == ProgressState.SUCCESS) { |
| success.set(true); |
| } |
| }); |
| Thread.sleep(200); |
| } |
| |
| // overview |
| testGet("/details"); |
| testGet("/biggestObjects"); |
| |
| // class loader |
| testGet("/classLoaderExplorer/summary"); |
| testGet("/classLoaderExplorer/classLoader", |
| req -> req.addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10"), |
| resp -> { |
| Type type = new TypeToken<PageView<Record>>() { |
| }.getType(); |
| PageView<Record> result = GSON.fromJson(resp.bodyAsString(), type); |
| holder.id = result.getData().get(0).getObjectId(); |
| }); |
| testGet("/classLoaderExplorer/children", |
| (PreProcessor) req -> req.addQueryParam("classLoaderId", String.valueOf(holder.id)) |
| .addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10")); |
| |
| // class reference |
| testGet("/classReference/inbounds/class", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id))); |
| testGet("/classReference/outbounds/class", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id))); |
| |
| // direct byte buffer |
| testGet("/directByteBuffer/summary"); |
| testGet("/directByteBuffer/records", |
| (PreProcessor) req -> req.addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10")); |
| |
| // dominator tree |
| testGet("/dominatorTree/roots", |
| (PreProcessor) req -> req.addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10") |
| .addQueryParam("grouping", "NONE")); |
| testGet("/dominatorTree/children", |
| (PreProcessor) req -> req.addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10") |
| .addQueryParam("grouping", "NONE") |
| .addQueryParam("parentObjectId", String.valueOf(holder.id))); |
| |
| // gc root |
| testGet("/GCRoots"); |
| testGet("/GCRoots/classes", |
| (PreProcessor) req -> req.addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10") |
| .addQueryParam("rootTypeIndex", "1")); |
| testGet("/GCRoots/class/objects", |
| (PreProcessor) req -> req.addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10") |
| .addQueryParam("rootTypeIndex", "1") |
| .addQueryParam("classIndex", "1")); |
| |
| // histogram |
| testGet("/histogram", |
| (PreProcessor) req -> req.addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10") |
| .addQueryParam("groupingBy", "BY_CLASS")); |
| |
| // inspector |
| testGet("/inspector/objectView", |
| req -> req.addQueryParam("objectId", String.valueOf(holder.id)), |
| resp -> { |
| ObjectView objectView = GSON.fromJson(resp.bodyAsString(), ObjectView.class); |
| holder.objectAddress = objectView.getObjectAddress(); |
| }); |
| testGet("/inspector/addressToId", |
| (PreProcessor) req -> req.addQueryParam("objectAddress", String.valueOf(holder.objectAddress))); |
| testGet("/inspector/value", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id))); |
| testGet("/inspector/fields", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id)) |
| .addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10")); |
| testGet("/inspector/staticFields", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id)) |
| .addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10")); |
| |
| // leak report |
| testGet("/leak/report"); |
| |
| // object list |
| testGet("/outbounds", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id)) |
| .addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10")); |
| testGet("/inbounds", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id)) |
| .addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10")); |
| |
| // object |
| testGet("/object", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id))); |
| |
| // oql |
| testGet("/oql", |
| (PreProcessor) req -> req.addQueryParam("oql", "select * from java.lang.String") |
| .addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10")); |
| |
| // path to gc roots |
| testGet("/pathToGCRoots", |
| (PreProcessor) req -> req.addQueryParam("origin", String.valueOf(holder.id)) |
| .addQueryParam("skip", "0") |
| .addQueryParam("count", "10")); |
| |
| // system property |
| testGet("/systemProperties"); |
| |
| // thread |
| testGet("/threadsSummary"); |
| testGet("/threads", |
| req -> req.addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10"), |
| resp -> { |
| Type type = new TypeToken<PageView<Info>>() { |
| }.getType(); |
| PageView<Info> result = GSON.fromJson(resp.bodyAsString(), type); |
| holder.id = result.getData().get(0).getObjectId(); |
| } |
| ); |
| testGet("/stackTrace", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id))); |
| testGet("/locals", |
| (PreProcessor) req -> req.addQueryParam("objectId", String.valueOf(holder.id)) |
| .addQueryParam("depth", "1") |
| .addQueryParam("firstNonNativeFrame", "false")); |
| |
| |
| // unreachable objects |
| testGet("/unreachableObjects/summary"); |
| testGet("/unreachableObjects/records", |
| (PreProcessor) req -> req.addQueryParam("page", "1") |
| .addQueryParam("pageSize", "10")); |
| } |
| |
| static void testGet(String uri) { |
| testGet(uri, null, null); |
| } |
| |
| static void testGet(String uri, PreProcessor processor) { |
| testGet(uri, processor, null); |
| } |
| |
| static void testGet(String uri, PostProcessor postProcessor) { |
| testGet(uri, null, postProcessor); |
| } |
| |
| static void testGet(String uri, PreProcessor processor, PostProcessor postProcessor) { |
| test(uri, HttpMethod.GET, processor, postProcessor); |
| } |
| |
| static void testPost(String uri) { |
| test(uri, HttpMethod.POST, null, null); |
| } |
| |
| static void test(String uri, HttpMethod method, PreProcessor processor, PostProcessor postProcessor) { |
| LOGGER.info("test {}", uri); |
| Async async = context.async(); |
| HttpRequest<Buffer> request = |
| CLIENT.request(method, Global.PORT, Global.HOST, uri("/heap-dump/" + TEST_HEAP_DUMP_FILENAME + uri)); |
| if (processor != null) { |
| processor.process(request); |
| } |
| request.send( |
| ar -> { |
| context.assertTrue(ar.succeeded()); |
| context.assertEquals(ar.result().statusCode(), |
| method == HttpMethod.GET ? HTTP_GET_OK_STATUS_CODE : HTTP_POST_CREATED_STATUS_CODE, |
| ar.result().bodyAsString()); |
| |
| if (postProcessor != null) { |
| postProcessor.process(ar.result()); |
| } |
| LOGGER.info("{}: {}", uri, ar.result().bodyAsString()); |
| async.complete(); |
| } |
| ); |
| async.awaitSuccess(); |
| } |
| |
| interface PreProcessor { |
| void process(HttpRequest<Buffer> request); |
| } |
| |
| interface PostProcessor { |
| void process(HttpResponse<Buffer> resp); |
| } |
| |
| static class Holder { |
| int id; |
| |
| long objectAddress; |
| } |
| } |