diff options
author | Jon Bratseth <bratseth@gmail.com> | 2021-03-25 13:01:09 +0100 |
---|---|---|
committer | Jon Bratseth <bratseth@gmail.com> | 2021-03-25 13:01:09 +0100 |
commit | f97a835dea395f0d18256535c559bd66746cf04d (patch) | |
tree | e8601ab9c2366b3f06a92c329465eecf72e1ed26 | |
parent | 748f6279d83e25183e9ed1053f81f4ea0204008b (diff) |
Add stats to controller/v1 API
13 files changed, 189 insertions, 13 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Load.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Load.java index a16d05a7310..d3fef13aa77 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Load.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Load.java @@ -24,4 +24,6 @@ public class Load { return "load: cpu " + cpu + ", memory " + memory + ", disk " + disk; } + public static Load zero() { return new Load(0, 0, 0); } + } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepoStats.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepoStats.java index 31e5730b6a6..5b5ac21b500 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepoStats.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepoStats.java @@ -10,7 +10,7 @@ public class NodeRepoStats { private final Load load; private final Load activeLoad; - private List<ApplicationStats> applicationStats; + private final List<ApplicationStats> applicationStats; public NodeRepoStats(Load load, Load activeLoad, List<ApplicationStats> applicationStats) { this.load = load; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java index bbe982bd5fe..7d454b2134f 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java @@ -79,6 +79,8 @@ public interface NodeRepository { void patchApplication(ZoneId zone, ApplicationId application, double currentReadShare, double maxReadShare); + NodeRepoStats getStats(ZoneId zone); + Map<TenantName, URI> getArchiveUris(ZoneId zone); void setArchiveUri(ZoneId zone, TenantName tenantName, URI archiveUri); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeRepoStatsData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeRepoStatsData.java index d3a81201c78..a4b3174b442 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeRepoStatsData.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/NodeRepoStatsData.java @@ -22,12 +22,12 @@ public class NodeRepoStatsData { @JsonProperty("activeLoad") public LoadData activeLoad; - @JsonProperty("applicationStats") - public List<ApplicationStatsData> applicationStats; + @JsonProperty("applications") + public List<ApplicationStatsData> applications; public NodeRepoStats toNodeRepoStats() { return new NodeRepoStats(load.toLoad(), activeLoad.toLoad(), - applicationStats.stream().map(stats -> stats.toApplicationStats()).collect(Collectors.toList())); + applications.stream().map(stats -> stats.toApplicationStats()).collect(Collectors.toList())); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java index 10c7452a6af..98a9ade1b16 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java @@ -68,6 +68,7 @@ public class ControllerApiHandler extends AuditLoggingRequestHandler { if (path.matches("/controller/v1/")) return root(request); if (path.matches("/controller/v1/auditlog/")) return new AuditLogResponse(controller.auditLogger().readLog()); if (path.matches("/controller/v1/maintenance/")) return new JobsResponse(controller.jobControl()); + if (path.matches("/controller/v1/stats")) return new StatsResponse(controller); if (path.matches("/controller/v1/jobs/upgrader")) return new UpgraderResponse(maintenance.upgrader()); if (path.matches("/controller/v1/metering/tenant/{tenant}/month/{month}")) return new MeteringResponse(controller.serviceRegistry().meteringService(), path.get("tenant"), path.get("month")); return notFound(path); @@ -94,7 +95,7 @@ public class ControllerApiHandler extends AuditLoggingRequestHandler { private HttpResponse notFound(Path path) { return ErrorResponse.notFoundError("Nothing at " + path); } private HttpResponse root(HttpRequest request) { - return new ResourceResponse(request, "auditlog", "jobs/upgrader", "maintenance"); + return new ResourceResponse(request, "auditlog", "maintenance", "stats", "jobs/upgrader", "metering/tenant"); } private HttpResponse configureUpgrader(HttpRequest request) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/StatsResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/StatsResponse.java new file mode 100644 index 00000000000..e68026f2192 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/StatsResponse.java @@ -0,0 +1,61 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.restapi.controller; + +import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.restapi.SlimeJsonResponse; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.ApplicationStats; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Load; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepoStats; + +/** + * A response containing statistics about this controller and its zones. + * + * @author bratseth + */ +public class StatsResponse extends SlimeJsonResponse { + + public StatsResponse(Controller controller) { + super(toSlime(controller)); + } + + private static Slime toSlime(Controller controller) { + try { + Slime slime = new Slime(); + Cursor root = slime.setObject(); + Cursor zonesArray = root.setArray("zones"); + for (ZoneId zone : controller.zoneRegistry().zones().all().ids()) { + NodeRepoStats stats = controller.serviceRegistry().configServer().nodeRepository().getStats(zone); + if (stats.applicationStats().isEmpty()) continue; // skip empty zones + Cursor zoneObject = zonesArray.addObject(); + zoneObject.setString("id", zone.toString()); + toSlime(stats.load(), zoneObject.setObject("load")); + toSlime(stats.load(), zoneObject.setObject("activeLoad")); + Cursor applicationsArray = zoneObject.setArray("applications"); + for (var applicationStats : stats.applicationStats()) + toSlime(applicationStats, applicationsArray.addObject()); + } + return slime; + } + catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + private static void toSlime(ApplicationStats stats, Cursor applicationObject) { + applicationObject.setString("id", stats.id().toFullString()); + toSlime(stats.load(), applicationObject.setObject("load")); + applicationObject.setDouble("cost", stats.cost()); + applicationObject.setDouble("unutilizedCost", stats.unutilizedCost()); + } + + private static void toSlime(Load load, Cursor loadObject) { + loadObject.setDouble("cpu", load.cpu()); + loadObject.setDouble("memory", load.memory()); + loadObject.setDouble("disk", load.disk()); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java index 85e82bb3fcd..2eabc5dc21b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java @@ -12,7 +12,10 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Application; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.ApplicationStats; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Load; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepoStats; import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeRepository; import com.yahoo.vespa.hosted.controller.api.integration.configserver.TargetVersions; import com.yahoo.vespa.hosted.controller.api.integration.noderepository.NodeList; @@ -66,6 +69,18 @@ public class NodeRepositoryMock implements NodeRepository { applications.get(zone).put(application.id(), application); } + @Override + public NodeRepoStats getStats(ZoneId zone) { + List<ApplicationStats> applicationStats = + applications.containsKey(zone) + ? applications.get(zone).keySet().stream() + .map(id -> new ApplicationStats(id, Load.zero(), 0, 0)) + .collect(Collectors.toList()) + : List.of(); + + return new NodeRepoStats(Load.zero(), Load.zero(), applicationStats); + } + public Pair<Double, Double> getTrafficFraction(ApplicationId application, ZoneId zone) { return trafficFractions.get(new DeploymentId(application, zone)); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java index 5523ce8dd9f..8f6988dbc27 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java @@ -8,8 +8,10 @@ import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.test.ManualClock; import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.flags.PermanentFlags; +import com.yahoo.vespa.hosted.controller.api.integration.configserver.Application; import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot; import com.yahoo.vespa.hosted.controller.auditlog.AuditLogger; +import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryMock; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; import org.junit.Before; @@ -50,6 +52,20 @@ public class ControllerApiTest extends ControllerContainerTest { } @Test + public void testStats() { + var mock = (NodeRepositoryMock)tester.controller().serviceRegistry().configServer().nodeRepository(); + mock.putApplication(ZoneId.from("prod", "us-west-1"), + new Application(ApplicationId.fromFullString("t1.a1.i1"), List.of())); + mock.putApplication(ZoneId.from("prod", "us-west-1"), + new Application(ApplicationId.fromFullString("t2.a2.i2"), List.of())); + mock.putApplication(ZoneId.from("prod", "us-east-3"), + new Application(ApplicationId.fromFullString("t1.a1.i1"), List.of())); + + tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/stats", "", Request.Method.GET), + new File("stats.json")); + } + + @Test public void testUpgraderApi() { // Get current configuration tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/jobs/upgrader", "", Request.Method.GET), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/root.json index 0ff5a8bf443..6d800db303c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/root.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/root.json @@ -4,10 +4,16 @@ "url": "http://localhost:8080/controller/v1/auditlog/" }, { + "url": "http://localhost:8080/controller/v1/maintenance/" + }, + { + "url": "http://localhost:8080/controller/v1/stats/" + }, + { "url": "http://localhost:8080/controller/v1/jobs/upgrader/" }, { - "url": "http://localhost:8080/controller/v1/maintenance/" + "url": "http://localhost:8080/controller/v1/metering/tenant/" } ] } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/stats.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/stats.json new file mode 100644 index 00000000000..1c9230798dd --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/stats.json @@ -0,0 +1,64 @@ +{ + "zones": [ + { + "id": "prod.us-east-3", + "load": { + "cpu": 0.0, + "memory": 0.0, + "disk": 0.0 + }, + "activeLoad": { + "cpu": 0.0, + "memory": 0.0, + "disk": 0.0 + }, + "applications": [ + { + "id": "t1.a1.i1", + "load": { + "cpu": 0.0, + "memory": 0.0, + "disk": 0.0 + }, + "cost": 0.0, + "unutilizedCost": 0.0 + } + ] + }, + { + "id": "prod.us-west-1", + "load": { + "cpu": 0.0, + "memory": 0.0, + "disk": 0.0 + }, + "activeLoad": { + "cpu": 0.0, + "memory": 0.0, + "disk": 0.0 + }, + "applications": [ + { + "id": "t1.a1.i1", + "load": { + "cpu": 0.0, + "memory": 0.0, + "disk": 0.0 + }, + "cost": 0.0, + "unutilizedCost": 0.0 + }, + { + "id": "t2.a2.i2", + "load": { + "cpu": 0.0, + "memory": 0.0, + "disk": 0.0 + }, + "cost": 0.0, + "unutilizedCost": 0.0 + } + ] + } + ] +}
\ No newline at end of file diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java index 750fe4f0538..9fd23d0d2c2 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java @@ -112,7 +112,7 @@ public class NodesV2ApiHandler extends LoggingRequestHandler { private HttpResponse handleGET(HttpRequest request) { Path path = new Path(request.getUri()); String pathS = request.getUri().getPath(); - if (path.matches( "/nodes/v2")) return new ResourceResponse(request.getUri(), "state", "node", "command", "maintenance", "upgrade", "application", "archive", "locks"); + if (path.matches( "/nodes/v2")) return new ResourceResponse(request.getUri(), "node", "state", "acl", "command", "archive", "locks", "maintenance", "upgrade", "capacity", "application", "stats"); if (path.matches( "/nodes/v2/node")) return new NodesResponse(ResponseType.nodeList, request, orchestrator, nodeRepository); if (pathS.startsWith("/nodes/v2/node/")) return new NodesResponse(ResponseType.singleNode, request, orchestrator, nodeRepository); if (path.matches( "/nodes/v2/state")) return new NodesResponse(ResponseType.stateList, request, orchestrator, nodeRepository); @@ -462,7 +462,7 @@ public class NodesV2ApiHandler extends LoggingRequestHandler { toSlime(stats.load(), root.setObject("load")); toSlime(stats.load(), root.setObject("activeLoad")); - Cursor applicationsObject = root.setArray("applicationStats"); + Cursor applicationsObject = root.setArray("applications"); for (int i = 0; i <= 5; i++) { if (i >= stats.applicationStats().size()) break; diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/root.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/root.json index 91b513cd9bc..201cc47d66e 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/root.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/root.json @@ -1,28 +1,37 @@ { "resources": [ { + "url": "http://localhost:8080/nodes/v2/node/" + }, + { "url": "http://localhost:8080/nodes/v2/state/" }, { - "url": "http://localhost:8080/nodes/v2/node/" + "url": "http://localhost:8080/nodes/v2/acl/" }, { "url": "http://localhost:8080/nodes/v2/command/" }, { + "url":"http://localhost:8080/nodes/v2/archive/" + }, + { + "url":"http://localhost:8080/nodes/v2/locks/" + }, + { "url": "http://localhost:8080/nodes/v2/maintenance/" }, { "url":"http://localhost:8080/nodes/v2/upgrade/" }, { - "url":"http://localhost:8080/nodes/v2/application/" + "url":"http://localhost:8080/nodes/v2/capacity/" }, { - "url":"http://localhost:8080/nodes/v2/archive/" + "url":"http://localhost:8080/nodes/v2/application/" }, { - "url":"http://localhost:8080/nodes/v2/locks/" + "url":"http://localhost:8080/nodes/v2/stats/" } ] }
\ No newline at end of file diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/stats.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/stats.json index c8e41078a7a..8867520fef6 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/stats.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/stats.json @@ -9,7 +9,7 @@ "memory": 0.0, "disk": 0.0 }, - "applicationStats": [ + "applications": [ { "id": "tenant3.application3.instance3", "load": { |