diff options
19 files changed, 129 insertions, 517 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceInformation.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceInformation.java index c8f913d82a8..391aaa4639e 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceInformation.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/application/v4/model/InstanceInformation.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.controller.api.application.v4.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.yahoo.vespa.hosted.controller.api.integration.cost.restapi.CostJsonModel; import com.yahoo.vespa.hosted.controller.api.identifiers.GitBranch; import com.yahoo.vespa.hosted.controller.api.identifiers.GitCommit; import com.yahoo.vespa.hosted.controller.api.identifiers.GitRepository; @@ -29,6 +28,4 @@ public class InstanceInformation { public GitRepository gitRepository; public GitBranch gitBranch; public GitCommit gitCommit; - - public CostJsonModel.Application cost; } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Cost.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Cost.java deleted file mode 100644 index 70fce483c38..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/Cost.java +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.integration.cost; - -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.hosted.controller.common.NotFoundCheckedException; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Calculate cost, hardware utilization and waste for Vespa. - * <p> - * Cost refers to the total cost ownership aka TCO. - * <p> - * We define a target utilization for each cluster in an application and compares this - * to the actual utilization. Cost is then the TCO for the hardware used multiplied with - * the relative utilization. Waste is the difference between target and ideal cost. - * - * @author smorgrav - */ -public interface Cost { - - /** - * Get application costs for all applications across all zones. - * - * @return A list of all application costs in all zones - */ - default List<CostApplication> getApplicationCost() { - List<CostApplication> costApplications = new ArrayList<>(); - getApplications().forEach((zone, list) -> { - list.forEach(app -> { - try { - costApplications.add(getApplicationCost(zone, app)); - } catch (NotFoundCheckedException e) { - // Application removed after fetched in getApplications ? - // TODO Log - } - }); - - }); - - return costApplications; - } - - /** - * Get application costs for a specific application. - * - * @param zone The zone - the combination of a environment and region e.g 'test.us-east-1' - * @param app ApplicationId e.g tenant:application:instance - * @return The cost of one application - */ - default CostApplication getApplicationCost(Zone zone, ApplicationId app) - throws NotFoundCheckedException { - - Map<String, CostClusterInfo> info = getClusterInfo(zone, app); - Map<String, CostResources> util = getClusterUtilization(zone, app); - CostResources target = getTargetUtilization(zone, app); - - Map<String, CostCluster> costClusters = new HashMap<>(); - for (String clusterId : util.keySet()) { - costClusters.put(clusterId, new CostCluster(info.get(clusterId), util.get(clusterId), target)); - } - - return new CostApplication(zone, app, costClusters); - } - - /** - * Provides target utilization - default targets are cased on the following assumptions: - * - * 1. CPU contention starts to be noticeable at 80% and spikes are 2x average - * 2. Query load is perfectly load-balanced between two zones, cpu needs to handle fail-over - thus 2x load - * 3. Memory contention (and especially sys cpu usage from memory management) increases after 90% - * 4. Memory consumptions spikes over average with ~20% - * 5. Memory and disk usage is independent of query load - * - * The default targets are: - * CPU: 0.2 - * MEM: 0.7 - * DISK: 0.7 - * DISKBUSY: 0.3 - * - * @param zone The zone - the combination of a environment and region e.g 'test.us-east-1' - * @param app ApplicationId e.g tenant:application:instance - * @return Target utilization - */ - default CostResources getTargetUtilization(Zone zone, ApplicationId app) { - return new CostResources(0.7, 0.2, 0.7, 0.3); - } - - /** - * @return Map from zone to app for all known zones and applications - */ - Map<Zone, List<ApplicationId>> getApplications(); - - /** - * Provides information about the clusters in the application like - * what hardware that it is using, the TCO for the hardware and the hostnames in the cluster. - * - * @param zone The zone - the combination of a environment and region e.g 'test.us-east-1' - * @param app ApplicationId e.g tenant:application:instance - * @return Map between cluster id to cluster info - */ - Map<String, CostClusterInfo> getClusterInfo(Zone zone, ApplicationId app); - - /** - * Provides the ratio of available hardware used (e.g cpu, mem, disk) each in the range: [0,1]. - * - * @param zone The zone - the combination of a environment and region e.g 'test.us-east-1' - * @param app ApplicationId e.g tenant:application:instance - * @return Map between cluster id to resource utilization - */ - Map<String, CostResources> getClusterUtilization(Zone zone, ApplicationId app); -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/package-info.java deleted file mode 100644 index f08e6cc9b36..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -@ExportPackage -package com.yahoo.vespa.hosted.controller.api.integration.cost; - -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/CostJsonModel.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/CostJsonModel.java deleted file mode 100644 index d384baa94ca..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/CostJsonModel.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.integration.cost.restapi; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; -import java.util.Map; - -/** - * JSON datamodel for the cost api. - * - * @author smorgrav - */ -public class CostJsonModel { - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Application { - - @JsonProperty - public String zone; - @JsonProperty - public String tenant; - @JsonProperty - public String app; - @JsonProperty - public int tco; - @JsonProperty - public double utilization; - @JsonProperty - public double waste; - @JsonProperty - public Map<String, Cluster> cluster; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Cluster { - - @JsonProperty - public int count; - @JsonProperty - public String resource; - @JsonProperty - public double utilization; - @JsonProperty - public int tco; - @JsonProperty - public String flavor; - @JsonProperty - public int waste; - @JsonProperty - public String type; - @JsonProperty - public HardwareResources util; - @JsonProperty - public HardwareResources usage; - @JsonProperty - public List<String> hostnames; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class HardwareResources { - - @JsonProperty - public double mem; - @JsonProperty - public double disk; - @JsonProperty - public double cpu; - @JsonProperty("diskbusy") - public double diskBusy; - } -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/CostJsonModelAdapter.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/CostJsonModelAdapter.java deleted file mode 100644 index 101d56f134c..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/CostJsonModelAdapter.java +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.integration.cost.restapi; - -import com.yahoo.config.provision.Flavor; -import com.yahoo.slime.Cursor; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostApplication; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostCluster; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostResources; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/** - * Serializing and de-serializing cost model - * - * @author smorgrav - */ -public class CostJsonModelAdapter { - - public static CostJsonModel.Application toJsonModel(CostApplication appCost) { - CostJsonModel.Application app = new CostJsonModel.Application(); - app.zone = appCost.getZone().toString(); - app.tenant = appCost.getAppId().tenant().value(); - app.app = appCost.getAppId().application().value(); - app.tco = (int)appCost.getTco(); - app.utilization = appCost.getUtilization(); - app.waste = appCost.getWaste(); - app.cluster = new HashMap<>(); - Map<String, CostCluster> clusterMap = appCost.getCluster(); - for (String key : clusterMap.keySet()) { - app.cluster.put(key, toJsonModel(clusterMap.get(key))); - } - - return app; - } - - public static void toSlime(CostApplication appCost, Cursor object) { - object.setString("zone", appCost.getZone().toString()); - object.setString("tenant", appCost.getAppId().tenant().value()); - object.setString("app", appCost.getAppId().application().value() + "." + appCost.getAppId().instance().value()); - object.setLong("tco", (long)appCost.getTco()); - object.setDouble("utilization", appCost.getUtilization()); - object.setDouble("waste", appCost.getWaste()); - Cursor clustersObject = object.setObject("cluster"); - for (Map.Entry<String, CostCluster> clusterEntry : appCost.getCluster().entrySet()) - toSlime(clusterEntry.getValue(), clustersObject.setObject(clusterEntry.getKey())); - } - - public static CostJsonModel.Cluster toJsonModel(CostCluster clusterCost) { - CostJsonModel.Cluster cluster = new CostJsonModel.Cluster(); - cluster.count = clusterCost.getClusterInfo().getHostnames().size(); - cluster.resource = getResourceName(clusterCost.getResultUtilization()); - cluster.utilization = clusterCost.getResultUtilization().getMaxUtilization(); - cluster.tco = (int)clusterCost.getTco(); - cluster.flavor = clusterCost.getClusterInfo().getFlavor().toString(); - cluster.waste = (int)clusterCost.getWaste(); - cluster.type = clusterCost.getClusterInfo().getClusterType().name(); - cluster.util = new CostJsonModel.HardwareResources(); - cluster.util.cpu = clusterCost.getResultUtilization().getCpu(); - cluster.util.mem = clusterCost.getResultUtilization().getMemory(); - cluster.util.disk = clusterCost.getResultUtilization().getDisk(); - cluster.util.diskBusy = clusterCost.getResultUtilization().getDiskBusy(); - - Flavor flavor = clusterCost.getClusterInfo().getFlavor(); - cluster.usage = new CostJsonModel.HardwareResources(); - cluster.usage.cpu = flavor.getMinCpuCores() * clusterCost.getSystemUtilization().getCpu(); - cluster.usage.mem = flavor.getMinMainMemoryAvailableGb() * clusterCost.getSystemUtilization().getMemory(); - cluster.usage.disk = flavor.getMinDiskAvailableGb() * clusterCost.getSystemUtilization().getDisk(); - cluster.usage.diskBusy = clusterCost.getSystemUtilization().getDiskBusy(); - cluster.hostnames = new ArrayList<>(clusterCost.getClusterInfo().getHostnames()); - - return cluster; - } - - private static void toSlime(CostCluster clusterCost, Cursor object) { - object.setLong("count", clusterCost.getClusterInfo().getHostnames().size()); - object.setString("resource", getResourceName(clusterCost.getResultUtilization())); - object.setDouble("utilization", clusterCost.getResultUtilization().getMaxUtilization()); - object.setLong("tco", (int)clusterCost.getTco()); - object.setString("flavor", clusterCost.getClusterInfo().getClusterType().name()); - object.setLong("waste", (int)clusterCost.getWaste()); - object.setString("type", clusterCost.getClusterInfo().getClusterType().name()); - Cursor utilObject = object.setObject("util"); - utilObject.setDouble("cpu", clusterCost.getResultUtilization().getCpu()); - utilObject.setDouble("mem", clusterCost.getResultUtilization().getMemory()); - utilObject.setDouble("disk", clusterCost.getResultUtilization().getDisk()); - utilObject.setDouble("diskBusy", clusterCost.getResultUtilization().getDiskBusy()); - Cursor usageObject = object.setObject("usage"); - usageObject.setDouble("cpu", clusterCost.getSystemUtilization().getCpu() * 100); - usageObject.setDouble("mem", clusterCost.getSystemUtilization().getMemory() * 100); - usageObject.setDouble("disk", clusterCost.getSystemUtilization().getDisk() * 100); - usageObject.setDouble("diskBusy", clusterCost.getSystemUtilization().getDiskBusy() * 100); - Cursor hostnamesArray = object.setArray("hostnames"); - for (String hostname : clusterCost.getClusterInfo().getHostnames()) - hostnamesArray.addString(hostname); - } - - private static String getResourceName(CostResources resources) { - String name = "cpu"; - double max = resources.getMaxUtilization(); - - if (resources.getMemory() == max) { - name = "mem"; - } else if (resources.getDisk() == max) { - name = "disk"; - } else if (resources.getDiskBusy() == max) { - name = "diskbusy"; - } - - return name; - } -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/CostResource.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/CostResource.java deleted file mode 100644 index 571460d666c..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/CostResource.java +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.integration.cost.restapi; - -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import java.util.List; - -/** - * Cost and Utilization APi for hosted Vespa. - * - * Used to give insight to PEG and application owners about - * TOC and if the application is reasonable scaled. - * - * @author smorgrav - */ -@Path("v1") -@Produces(MediaType.APPLICATION_JSON) -public interface CostResource { - - @GET - @Path("/analysis/cpu") - List<CostJsonModel.Application> getCPUAnalysis(); - - @GET - @Produces("text/csv") - @Path("/csv") - String getCSV(); - - @GET - @Path("/apps") - List<CostJsonModel.Application> getApplicationsCost(); - - @GET - @Path("/apps/{environment}/{region}/{application}") - CostJsonModel.Application getApplicationCost(@PathParam("application") String appName, - @PathParam("region") String regionName, - @PathParam("environment") String envName); -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/package-info.java deleted file mode 100644 index 3c3bb7e7076..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/restapi/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -@ExportPackage -package com.yahoo.vespa.hosted.controller.api.integration.cost.restapi; - -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java index 4a641e6cd45..4f5c2ead4da 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java @@ -18,8 +18,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.athens.Athens; import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsClient; import com.yahoo.vespa.hosted.controller.api.integration.chef.Chef; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerClient; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostApplication; -import com.yahoo.vespa.hosted.controller.api.integration.cost.Cost; import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; import com.yahoo.vespa.hosted.controller.api.integration.github.GitHub; @@ -28,7 +26,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingSe import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; -import com.yahoo.vespa.hosted.controller.common.NotFoundCheckedException; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; @@ -81,7 +78,6 @@ public class Controller extends AbstractComponent { private final EntityService entityService; private final GlobalRoutingService globalRoutingService; private final ZoneRegistry zoneRegistry; - private final Cost cost; private final ConfigServerClient configServerClient; private final MetricsService metricsService; private final Chef chefClient; @@ -98,19 +94,19 @@ public class Controller extends AbstractComponent { public Controller(ControllerDb db, CuratorDb curator, RotationRepository rotationRepository, GitHub gitHub, Jira jiraClient, EntityService entityService, GlobalRoutingService globalRoutingService, - ZoneRegistry zoneRegistry, Cost cost, ConfigServerClient configServerClient, + ZoneRegistry zoneRegistry, ConfigServerClient configServerClient, MetricsService metricsService, NameService nameService, RoutingGenerator routingGenerator, Chef chefClient, Athens athens) { this(db, curator, rotationRepository, gitHub, jiraClient, entityService, globalRoutingService, zoneRegistry, - cost, configServerClient, metricsService, nameService, routingGenerator, chefClient, + configServerClient, metricsService, nameService, routingGenerator, chefClient, Clock.systemUTC(), athens); } public Controller(ControllerDb db, CuratorDb curator, RotationRepository rotationRepository, GitHub gitHub, Jira jiraClient, EntityService entityService, GlobalRoutingService globalRoutingService, - ZoneRegistry zoneRegistry, Cost cost, ConfigServerClient configServerClient, + ZoneRegistry zoneRegistry, ConfigServerClient configServerClient, MetricsService metricsService, NameService nameService, RoutingGenerator routingGenerator, Chef chefClient, Clock clock, Athens athens) { Objects.requireNonNull(db, "Controller db cannot be null"); @@ -121,7 +117,6 @@ public class Controller extends AbstractComponent { Objects.requireNonNull(entityService, "EntityService cannot be null"); Objects.requireNonNull(globalRoutingService, "GlobalRoutingService cannot be null"); Objects.requireNonNull(zoneRegistry, "ZoneRegistry cannot be null"); - Objects.requireNonNull(cost, "Cost cannot be null"); Objects.requireNonNull(configServerClient, "ConfigServerClient cannot be null"); Objects.requireNonNull(metricsService, "MetricsService cannot be null"); Objects.requireNonNull(nameService, "NameService cannot be null"); @@ -136,7 +131,6 @@ public class Controller extends AbstractComponent { this.entityService = entityService; this.globalRoutingService = globalRoutingService; this.zoneRegistry = zoneRegistry; - this.cost = cost; this.configServerClient = configServerClient; this.metricsService = metricsService; this.chefClient = chefClient; @@ -175,12 +169,6 @@ public class Controller extends AbstractComponent { public Clock clock() { return clock; } - public CostApplication getApplicationCost(com.yahoo.config.provision.ApplicationId application, - com.yahoo.config.provision.Zone zone) - throws NotFoundCheckedException { - return cost.getApplicationCost(zone, application); - } - public URI getElkUri(Environment environment, RegionName region, DeploymentId deploymentId) { return elkUrl(zoneRegistry.getLogServerUri(environment, region), deploymentId); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostCluster.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ClusterCost.java index be0aea6122d..654c7553248 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostCluster.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ClusterCost.java @@ -1,31 +1,29 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.integration.cost; +package com.yahoo.vespa.hosted.controller.application; /** - * Calculate tco and waste for one cluster within one Vespa application in one zone. + * Calculate tco and waste for one cluster within one deployment. * * @author smorgrav */ -public class CostCluster { +public class ClusterCost { private final double tco; private final double waste; - private final CostClusterInfo clusterInfo; - private final CostResources systemUtilization; - private final CostResources targetUtilization; - private final CostResources resultUtilization; + private final ClusterInfo clusterInfo; + private final ClusterUtilization systemUtilization; + private final ClusterUtilization targetUtilization; + private final ClusterUtilization resultUtilization; /** * @param clusterInfo Value object with cluster info e.g. the TCO for the hardware used * @param systemUtilization Utilization of system resources (as ratios) - * @param targetUtilization Target utilization (ratios - usually less than 1.0) */ - public CostCluster(CostClusterInfo clusterInfo, - CostResources systemUtilization, - CostResources targetUtilization) { + public ClusterCost(ClusterInfo clusterInfo, + ClusterUtilization systemUtilization) { this.clusterInfo = clusterInfo; this.systemUtilization = systemUtilization; - this.targetUtilization = targetUtilization; + this.targetUtilization = new ClusterUtilization(0.7,0.2, 0.7, 0.3); this.resultUtilization = calculateResultUtilization(systemUtilization, targetUtilization); this.tco = clusterInfo.getFlavor().cost() * Math.min(1, this.resultUtilization.getMaxUtilization()); @@ -40,29 +38,29 @@ public class CostCluster { return waste; } - public CostClusterInfo getClusterInfo() { + public ClusterInfo getClusterInfo() { return clusterInfo; } - public CostResources getSystemUtilization() { + public ClusterUtilization getSystemUtilization() { return systemUtilization; } - public CostResources getTargetUtilization() { + public ClusterUtilization getTargetUtilization() { return targetUtilization; } - public CostResources getResultUtilization() { + public ClusterUtilization getResultUtilization() { return resultUtilization; } - static CostResources calculateResultUtilization(CostResources system, CostResources target) { + static ClusterUtilization calculateResultUtilization(ClusterUtilization system, ClusterUtilization target) { double cpu = ratio(system.getCpu(),target.getCpu()); double mem = ratio(system.getMemory(),target.getMemory()); double disk = ratio(system.getDisk(),target.getDisk()); double diskbusy = ratio(system.getDiskBusy(),target.getDiskBusy()); - return new CostResources(mem, cpu, disk, diskbusy); + return new ClusterUtilization(mem, cpu, disk, diskbusy); } private static double ratio(double a, double b) { diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostClusterInfo.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ClusterInfo.java index 0fe408352dc..569e97e4f53 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostClusterInfo.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ClusterInfo.java @@ -1,4 +1,4 @@ -package com.yahoo.vespa.hosted.controller.api.integration.cost;// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.application;// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Flavor; @@ -11,12 +11,12 @@ import java.util.List; * * @author smorgrav */ -public class CostClusterInfo { +public class ClusterInfo { private final Flavor flavor; private final ClusterSpec.Type clusterType; private final List<String> hostnames; - CostClusterInfo(Flavor flavor, ClusterSpec.Type clusterType, List<String> hostnames) { + ClusterInfo(Flavor flavor, ClusterSpec.Type clusterType, List<String> hostnames) { this.flavor = flavor; this.clusterType = clusterType; this.hostnames = hostnames; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostResources.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ClusterUtilization.java index bb5887ebc99..3f6e807a581 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostResources.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ClusterUtilization.java @@ -1,4 +1,4 @@ -package com.yahoo.vespa.hosted.controller.api.integration.cost;// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.application;// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. /** * System resources as _ratios_ of available resources. @@ -7,7 +7,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.cost;// Copyright 2017 * * @author smorgrav */ -public class CostResources { +public class ClusterUtilization { private final double memory; private final double cpu; @@ -24,7 +24,7 @@ public class CostResources { * @param disk Disk utilization * @param diskBusy Disk busy */ - public CostResources(double memory, double cpu, double disk, double diskBusy) { + public ClusterUtilization(double memory, double cpu, double disk, double diskBusy) { this.memory = memory; this.cpu = cpu; this.disk = disk; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java index 75e0f82cdcf..f537206d0fb 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java @@ -2,9 +2,12 @@ package com.yahoo.vespa.hosted.controller.application; import com.yahoo.component.Version; +import com.yahoo.config.provision.ClusterSpec.Id; import com.yahoo.config.provision.Zone; import java.time.Instant; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** @@ -18,6 +21,8 @@ public class Deployment { private final ApplicationRevision revision; private final Version version; private final Instant deployTime; + private final Map<Id, ClusterUtilization> clusterUtils = new HashMap<>(); + private final Map<Id, ClusterInfo> clusterInfo = new HashMap<>(); public Deployment(Zone zone, ApplicationRevision revision, Version version, Instant deployTime) { Objects.requireNonNull(zone, "zone cannot be null"); @@ -42,9 +47,31 @@ public class Deployment { /** Returns the time this was deployed */ public Instant at() { return deployTime; } + public Map<Id, ClusterUtilization> getClusterUtilization() { + return clusterUtils; + } + + public Map<Id, ClusterInfo> getClusterInfo() { + return clusterInfo; + } + + /** + * Calculate cost for this deployment. + * + * This is based on cluster utilization and cluster info. + */ + public DeploymentCost calculateCost() { + + Map<String, ClusterCost> costClusters = new HashMap<>(); + for (Id clusterId : clusterUtils.keySet()) { + costClusters.put(clusterId.value(), new ClusterCost(clusterInfo.get(clusterId), clusterUtils.get(clusterId))); + } + + return new DeploymentCost(costClusters); + } + @Override public String toString() { return "deployment to " + zone + " of " + revision + " on version " + version + " at " + deployTime; } - } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentCost.java index 89948cc69ac..b0bbbf7ca8c 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostApplication.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentCost.java @@ -1,8 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.api.integration.cost; - -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Zone; +package com.yahoo.vespa.hosted.controller.application; import java.util.HashMap; import java.util.Map; @@ -12,26 +9,26 @@ import java.util.Map; * * @author smorgrav */ -public class CostApplication { +public class DeploymentCost { - private final Zone zone; - private final ApplicationId appId; private final double utilization; private final double waste; private final double tco; - private final Map<String, CostCluster> clusters; - - public CostApplication(Zone zone, ApplicationId appId, Map<String, CostCluster> clusterCosts) { - this.zone = zone; - this.appId = appId; + private final Map<String, ClusterCost> clusters; + + public DeploymentCost() { + this(new HashMap<>()); + } + + public DeploymentCost(Map<String, ClusterCost> clusterCosts) { clusters = new HashMap<>(clusterCosts); double tco = 0; double util = 0; double waste = 0; - for (CostCluster costCluster : clusterCosts.values()) { + for (ClusterCost costCluster : clusterCosts.values()) { tco += costCluster.getTco(); waste += costCluster.getWaste(); int nodesInCluster = costCluster.getClusterInfo().getHostnames().size(); @@ -42,16 +39,8 @@ public class CostApplication { this.waste = waste; this.tco = tco; } - - public Zone getZone() { - return zone; - } - - public ApplicationId getAppId() { - return appId; - } - public Map<String, CostCluster> getCluster() { + public Map<String, ClusterCost> getCluster() { return clusters; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/RootHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/RootHandler.java index 9283b1c3018..50c4efc2d27 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/RootHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/RootHandler.java @@ -66,7 +66,6 @@ public class RootHandler extends LoggingRequestHandler { jerseyService(services, "statuspage", "/statuspage/v1/", "/statuspage/application.wadl"); jerseyService(services, "zone", "/zone/v1/", "/zone/application.wadl"); jerseyService(services, "zone", "/zone/v2/", "/zone/application.wadl"); - jerseyService(services, "cost", "/cost/v1/", "/cost/application.wadl"); handlerService(services, "application", "/application/v4/"); handlerService(services, "deployment", "/deployment/v1/"); handlerService(services, "screwdriver", "/screwdriver/v1/release/vespa"); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 2f11530b1f5..1fbfb1822ed 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -55,17 +55,17 @@ import com.yahoo.vespa.hosted.controller.api.integration.athens.NToken; import com.yahoo.vespa.hosted.controller.api.integration.athens.ZmsException; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; import com.yahoo.vespa.hosted.controller.api.integration.configserver.Log; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostApplication; -import com.yahoo.vespa.hosted.controller.api.integration.cost.restapi.CostJsonModelAdapter; import com.yahoo.vespa.hosted.controller.api.integration.routing.RotationStatus; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.ApplicationRevision; import com.yahoo.vespa.hosted.controller.application.Change; +import com.yahoo.vespa.hosted.controller.application.ClusterCost; +import com.yahoo.vespa.hosted.controller.application.ClusterUtilization; import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; +import com.yahoo.vespa.hosted.controller.application.DeploymentCost; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.SourceRevision; -import com.yahoo.vespa.hosted.controller.common.NotFoundCheckedException; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse; import com.yahoo.vespa.hosted.controller.restapi.MessageResponse; import com.yahoo.vespa.hosted.controller.restapi.Path; @@ -427,19 +427,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler { application.deploymentJobs().projectId().ifPresent(i -> response.setString("screwdriverId", String.valueOf(i))); sourceRevisionToSlime(deployment.revision().source(), response); - com.yahoo.config.provision.ApplicationId applicationId = com.yahoo.config.provision.ApplicationId.from(tenantName, applicationName, instanceName); - Zone zoneId = new Zone(Environment.from(environment), RegionName.from(region)); - // Cost - try { - CostApplication appCost = controller.getApplicationCost(applicationId, zoneId); - Cursor costObject = response.setObject("cost"); - CostJsonModelAdapter.toSlime(appCost, costObject); - } catch (NotFoundCheckedException nfce) { - log.log(Level.FINE, "Application cost data not found. " + nfce.getMessage()); - } + DeploymentCost appCost = deployment.calculateCost(); + Cursor costObject = response.setObject("cost"); + toSlime(appCost, costObject); // Metrics + com.yahoo.config.provision.ApplicationId applicationId = com.yahoo.config.provision.ApplicationId.from(tenantName, applicationName, instanceName); + Zone zoneId = new Zone(Environment.from(environment), RegionName.from(region)); try { MetricsService.DeploymentMetrics metrics = controller.metricsService().getDeploymentMetrics(applicationId, zoneId); Cursor metricsObject = response.setObject("metrics"); @@ -1087,4 +1082,50 @@ public class ApplicationApiHandler extends LoggingRequestHandler { return controller.systemVersion(); } + public static void toSlime(DeploymentCost deploymentCost, Cursor object) { + object.setLong("tco", (long)deploymentCost.getTco()); + object.setDouble("utilization", deploymentCost.getUtilization()); + object.setDouble("waste", deploymentCost.getWaste()); + Cursor clustersObject = object.setObject("cluster"); + for (Map.Entry<String, ClusterCost> clusterEntry : deploymentCost.getCluster().entrySet()) + toSlime(clusterEntry.getValue(), clustersObject.setObject(clusterEntry.getKey())); + } + + private static void toSlime(ClusterCost clusterCost, Cursor object) { + object.setLong("count", clusterCost.getClusterInfo().getHostnames().size()); + object.setString("resource", getResourceName(clusterCost.getResultUtilization())); + object.setDouble("utilization", clusterCost.getResultUtilization().getMaxUtilization()); + object.setLong("tco", (int)clusterCost.getTco()); + object.setString("flavor", clusterCost.getClusterInfo().getClusterType().name()); + object.setLong("waste", (int)clusterCost.getWaste()); + object.setString("type", clusterCost.getClusterInfo().getClusterType().name()); + Cursor utilObject = object.setObject("util"); + utilObject.setDouble("cpu", clusterCost.getResultUtilization().getCpu()); + utilObject.setDouble("mem", clusterCost.getResultUtilization().getMemory()); + utilObject.setDouble("disk", clusterCost.getResultUtilization().getDisk()); + utilObject.setDouble("diskBusy", clusterCost.getResultUtilization().getDiskBusy()); + Cursor usageObject = object.setObject("usage"); + usageObject.setDouble("cpu", clusterCost.getSystemUtilization().getCpu() * 100); + usageObject.setDouble("mem", clusterCost.getSystemUtilization().getMemory() * 100); + usageObject.setDouble("disk", clusterCost.getSystemUtilization().getDisk() * 100); + usageObject.setDouble("diskBusy", clusterCost.getSystemUtilization().getDiskBusy() * 100); + Cursor hostnamesArray = object.setArray("hostnames"); + for (String hostname : clusterCost.getClusterInfo().getHostnames()) + hostnamesArray.addString(hostname); + } + + private static String getResourceName(ClusterUtilization utilization) { + String name = "cpu"; + double max = utilization.getMaxUtilization(); + + if (utilization.getMemory() == max) { + name = "mem"; + } else if (utilization.getDisk() == max) { + name = "disk"; + } else if (utilization.getDiskBusy() == max) { + name = "diskbusy"; + } + + return name; + } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java index de6e1b9af35..2aebc8bddab 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java @@ -32,7 +32,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.jira.JiraMock; import com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.Change; -import com.yahoo.vespa.hosted.controller.cost.CostMock; import com.yahoo.vespa.hosted.controller.integration.MockMetricsService; import com.yahoo.vespa.hosted.controller.persistence.ControllerDb; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; @@ -78,7 +77,6 @@ public final class ControllerTester { new MemoryEntityService(), new MemoryGlobalRoutingService(), zoneRegistryMock, - new CostMock(), configServerClientMock, new MockMetricsService(), nameService, diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostResourcesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/ClusterUtilizationTest.java index a7ed2f90548..ca67e605682 100644 --- a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostResourcesTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/ClusterUtilizationTest.java @@ -1,29 +1,29 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -package com.yahoo.vespa.hosted.controller.api.integration.cost; +package com.yahoo.vespa.hosted.controller.application; import org.junit.Assert; import org.junit.Test; +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. /** * @author smorgrav */ -public class CostResourcesTest { +public class ClusterUtilizationTest { - private static double delta = Double.MIN_EXPONENT; + private static final double delta = Double.MIN_NORMAL; @Test public void getMaxUtilization() throws Exception { - CostResources resources = new CostResources(0.3, 0.1, 0.4, 0.5); + ClusterUtilization resources = new ClusterUtilization(0.3, 0.1, 0.4, 0.5); Assert.assertEquals(0.5, resources.getMaxUtilization(), delta); - resources = new CostResources(0.3, 0.1, 0.4, 0.0); + resources = new ClusterUtilization(0.3, 0.1, 0.4, 0.0); Assert.assertEquals(0.4, resources.getMaxUtilization(), delta); - resources = new CostResources(0.4, 0.3, 0.3, 0.0); + resources = new ClusterUtilization(0.4, 0.3, 0.3, 0.0); Assert.assertEquals(0.4, resources.getMaxUtilization(), delta); - resources = new CostResources(0.1, 0.3, 0.3, 0.0); + resources = new ClusterUtilization(0.1, 0.3, 0.3, 0.0); Assert.assertEquals(0.3, resources.getMaxUtilization(), delta); } + }
\ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/cost/CostMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/cost/CostMock.java deleted file mode 100644 index ae4ca35ecaf..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/cost/CostMock.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.cost; - -import com.yahoo.component.AbstractComponent; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.hosted.controller.api.integration.cost.Cost; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostApplication; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostClusterInfo; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostResources; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author mpolden - */ -public class CostMock extends AbstractComponent implements Cost { - - private final Map<ApplicationId, CostApplication> applicationCost = new HashMap<>(); - - @Override - public List<CostApplication> getApplicationCost() { - return new ArrayList<>(applicationCost.values()); - } - - @Override - public Map<Zone, List<ApplicationId>> getApplications() { - return null; - } - - @Override - public Map<String, CostClusterInfo> getClusterInfo(Zone zone, ApplicationId app) { - return null; - } - - @Override - public Map<String, CostResources> getClusterUtilization(Zone zone, ApplicationId app) { - return null; - } - - public void setApplicationCost(ApplicationId application, CostApplication cost) { - applicationCost.put(application, cost); - } -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index 4ac1cc82485..7deda5f68d7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -4,8 +4,6 @@ package com.yahoo.vespa.hosted.controller.restapi.application; import com.yahoo.application.container.handler.Request; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; -import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.Zone; import com.yahoo.vespa.hosted.controller.ConfigServerClientMock; import com.yahoo.vespa.hosted.controller.api.identifiers.AthensDomain; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; @@ -15,11 +13,8 @@ import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensDbMoc import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.AthensMock; import com.yahoo.vespa.hosted.controller.api.integration.athens.mock.ZmsClientFactoryMock; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostApplication; -import com.yahoo.vespa.hosted.controller.api.integration.cost.CostCluster; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; -import com.yahoo.vespa.hosted.controller.cost.CostMock; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; @@ -687,28 +682,6 @@ public class ApplicationApiTest extends ControllerContainerTest { AthensDbMock.Domain domain = mock.getSetup().domains.get(new AthensDomain(domainName)); domain.admin(new AthensPrincipal(new AthensDomain(athensScrewdriverDomain), new UserId(screwdriverUserId))); } - - private void addMockObservedApplicationCost(String tenant, String application, String instance) { - CostMock mock = (CostMock) container.components().getComponent("com.yahoo.vespa.hosted.controller.cost.MockInsightBackend"); - - CostCluster cost = new CostCluster(null, null, null); -/* cost.setCount(2); - cost.setResource("cpu"); - cost.setUtilization(1.0f); - cost.setTco(25); - cost.setFlavor("flavor1"); - cost.setWaste(10); - cost.setType("content"); - List<String> hostnames = new ArrayList<>(); - hostnames.add("host1"); - hostnames.add("host2"); - cost.setHostnames(hostnames); - Map<String, CostCluster> clusterCosts = new HashMap<>(); - clusterCosts.put("cluster1", cost);*/ - - mock.setApplicationCost(new ApplicationId.Builder().tenant(tenant).applicationName(application).instanceName(instance).build(), - new CostApplication(new Zone(Environment.prod, RegionName.from("prod.us-west-1")),null, null)); - } private void startAndTestChange(ContainerControllerTester controllerTester, ApplicationId application, long projectId, HttpEntity deployData) throws IOException { @@ -745,4 +718,6 @@ public class ApplicationApiTest extends ControllerContainerTest { "Deactivated " + stagingPath.replaceFirst("/application/v4/", "")); controllerTester.notifyJobCompletion(application, projectId, true, DeploymentJobs.JobType.stagingTest); } + + private void addMockObservedApplicationCost(String tenant, String application, String instance) {} } |