From cefd3ca7755b1b6ec04a21df4b8adeae02066673 Mon Sep 17 00:00:00 2001 From: toby Date: Mon, 25 Sep 2017 13:23:42 +0200 Subject: Move calculateions to CostCluster and CostApplication classes --- .../controller/api/integration/cost/Cost.java | 96 ++++++---- .../api/integration/cost/CostApplication.java | 55 +++--- .../api/integration/cost/CostCluster.java | 200 +++++---------------- .../api/integration/cost/CostHardwareInfo.java | 39 ---- .../api/integration/cost/CostResources.java | 57 ++++++ .../cost/restapi/CostJsonModelAdapter.java | 8 +- 6 files changed, 191 insertions(+), 264 deletions(-) delete mode 100644 controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostHardwareInfo.java create mode 100644 controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostResources.java (limited to 'controller-api') 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 index b22557dbcb0..6de1c5371bd 100644 --- 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 @@ -2,90 +2,110 @@ package com.yahoo.vespa.hosted.controller.api.integration.cost; 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.common.NotFoundCheckedException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; /** - * Cost domain model. - * + * Calculate cost, hardware utilization and waste for applications. + *

* Cost refers to the total cost ownership aka TCO. - * + *

* We define a target utilization for each cluster in an application and compares this * to the actual utilization to get a number for ideal cost (if ideally scaled) and waste. - * + *

* The target utilization is defined with the following in mind: - * 1 Application stats to see contention on CPU above 80% - * 2 It is scaled for a 50/50 load balancing between two zones (thus must be able to serve the other zone) - * 3 Peaks are 2x average wrt CPU - * 4 Memory contention is rising when over 80% + * 1 Application stats to see contention on CPU above 80% + * 2 It is scaled for a 50/50 load balancing between two zones (thus must be able to serve the other zone) + * 3 Peaks are 2x average wrt CPU + * 4 Memory contention is rising when over 80% * * @author smorgrav */ public interface Cost { - /** - * Collect all information and format it as a CSV blob for download. - * - * @return A String with comma separated values. Can be big! - */ - default String getCsvForLocalAnalysis() { - return null; - } - /** * Get application costs for all applications across all zones * - * @return A list of all application costs + * @return A list of all application costs in all zones */ default List getApplicationCost() { - return null; + List 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 given application instance in a given zone. - * - * Default implementation calls out to @see getApplicationCost - * - * @param env Environment like test, dev, perf, staging or prod - * @param region Region name like us-east-1 - * @param app ApplicationId like tenant:application:instance + * Get application costs for a specific application deployement * - * @return A list of applications in given zone + * @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 A list of applications cost in given zone */ - default CostApplication getApplicationCost(Environment env, RegionName region, ApplicationId app) - throws NotFoundCheckedException { - return null; + default CostApplication getApplicationCost(Zone zone, ApplicationId app) + throws NotFoundCheckedException { + + Map info = getClusterInfo(zone, app); + Map util = getClusterUtilization(zone, app); + CostResources target = getTargetUtilization(zone, app); + + Map 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 XXX * + * @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 CostHardwareInfo getUsageTarget(Environment env, RegionName region, ApplicationId app) { - return new CostHardwareInfo(0.8, 0.3, 0.4, 0.3); + default CostResources getTargetUtilization(Zone zone, ApplicationId app) { + return new CostResources(0.8, 0.3, 0.4, 0.3); } + /** + * @return zone->app for all known zones and applications + */ + Map> getApplications(); /** * Provides information about the clusters in the application like * what hardware that it is using, the TCO for the hardware and number of hosts. * + * @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 clusterid -> costclusterinfo */ Map getClusterInfo(Zone zone, ApplicationId app); /** - * Provides measurements as absolute numbers of hardware resources. - * + * Provides ratio of available hardware used. + *

* Used to calculate the utilization of the hardware. * - * @return Map between clusterid -> costhardwareinfo + * @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 clusterid -> resource utilization */ - Map getUsageMetrics(Zone zone, ApplicationId app); + Map getClusterUtilization(Zone zone, ApplicationId app); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostApplication.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostApplication.java index f41a3d7e183..7e683969ccb 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostApplication.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostApplication.java @@ -1,67 +1,64 @@ // 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 java.util.HashMap; import java.util.Map; /** - * Cost data model for an application instance. I.e one running vespa application in one zone. + * Calcalute cost for for an application instance. I.e one running vespa application in one zone. * * @author smorgrav */ -// TODO: Make the Application own this and rename to Cost -// TODO: Enforce constraints -// TODO: Remove application id elements public class CostApplication { - /** This contains environment.region */ private final Zone zone; - - private final String tenant; - - // This must contain applicationName.instanceName. TODO: Fix - private final String app; - - private final int tco; + private final ApplicationId appId; private final double utilization; private final double waste; - private final Map cluster; + private final double tco; + private final Map clusters; - public CostApplication(Zone zone, String tenant, String app, int tco, float utilization, float waste, - Map clusterCost) { - if (utilization < 0) throw new IllegalArgumentException("Utilization cannot be negative"); + public CostApplication(Zone zone, ApplicationId appId, Map clusterCosts) { this.zone = zone; - this.tenant = tenant; - this.app = app; - this.tco = tco; - this.utilization = utilization; + this.appId = appId; + clusters = new HashMap<>(clusterCosts); + + double tco = 0; + double util = 0; + double waste = 0; + + for (CostCluster costCluster : clusterCosts.values()) { + tco += costCluster.getTco(); + waste += costCluster.getWaste(); + int nodesInCluster = costCluster.getClusterInfo().getHostnames().size(); + util = Math.max(util, nodesInCluster*costCluster.getResultUtilization().getMaxUtilization()); + } + + this.utilization = util; this.waste = waste; - cluster = new HashMap<>(clusterCost); + this.tco = tco; } public Zone getZone() { return zone; } - public String getApp() { - return app; + public ApplicationId getAppId() { + return appId; } public Map getCluster() { - return cluster; + return clusters; } - public int getTco() { + public double getTco() { return tco; } - public String getTenant() { - return tenant; - } - public double getUtilization() { return utilization; } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostCluster.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostCluster.java index e9920f36bca..37bf62e5b9e 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostCluster.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostCluster.java @@ -1,181 +1,73 @@ // 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 java.util.List; - /** - * Cost data model for a cluster. I.e one cluster within one Vespa application in one zone. + * Calculate tco, waste and result utilization + * for one cluster within one Vespa application in one zone. * * @author smorgrav */ -// TODO: Make immutable -// TODO: Enforce constraints -// TODO: Document content public class CostCluster { - - private int count; - private String resource; - private double utilization; - private int tco; - private String flavor; - private int waste; - private String type; - private double utilMem; - private double utilCpu; - private double utilDisk; - private double utilDiskBusy; - private double usageMem; - private double usageCpu; - private double usageDisk; - private double usageDiskBusy; - private List hostnames; - - /** Create an empty (invalid) cluster cost */ - public CostCluster() {} - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public String getFlavor() { - return flavor; - } - - public void setFlavor(String flavor) { - this.flavor = flavor; - } - - public List getHostnames() { - return hostnames; - } - - public void setHostnames(List hostnames) { - this.hostnames = hostnames; - } - - public String getResource() { - return resource; - } - - public void setResource(String resource) { - this.resource = resource; - } - - public int getTco() { + private final double tco; + private final double waste; + private final CostClusterInfo clusterInfo; + private final CostResources systemUtilization; + private final CostResources targetUtilization; + private final CostResources resultUtilization; + + /** + * @param clusterInfo Value object with cluster info e.g. hardware tco + * @param systemUtilization Utilization of system resources (in percentage) + * @param targetUtilization Target utilization (usually < 1.0) + */ + public CostCluster(CostClusterInfo clusterInfo, + CostResources systemUtilization, + CostResources targetUtilization) { + + this.clusterInfo = clusterInfo; + this.systemUtilization = systemUtilization; + this.targetUtilization = targetUtilization; + this.resultUtilization = calculateResultUtilization(systemUtilization, targetUtilization); + + this.tco = clusterInfo.getFlavor().cost() * Math.min(1, this.resultUtilization.getMaxUtilization()); + this.waste = clusterInfo.getFlavor().cost() - tco; + } + + public double getTco() { return tco; } - public void setTco(int tco) { - this.tco = tco; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public double getUtilization() { - return utilization; - } - - public void setUtilization(float utilization) { - validateUtilRatio(utilization); - this.utilization = utilization; - } - - public int getWaste() { + public double getWaste() { return waste; } - public void setWaste(int waste) { - this.waste = waste; - } - - public double getUsageCpu() { - return usageCpu; - } - - public void setUsageCpu(float usageCpu) { - validateUsageRatio(usageCpu); - this.usageCpu = usageCpu; - } - - public double getUsageDisk() { - return usageDisk; - } - - public void setUsageDisk(float usageDisk) { - validateUsageRatio(usageDisk); - this.usageDisk = usageDisk; - } - - public double getUsageMem() { - return usageMem; - } - - public void setUsageMem(float usageMem) { - validateUsageRatio(usageMem); - this.usageMem = usageMem; + public CostClusterInfo getClusterInfo() { + return clusterInfo; } - public double getUtilCpu() { - return utilCpu; + public CostResources getSystemUtilization() { + return systemUtilization; } - public void setUtilCpu(float utilCpu) { - validateUtilRatio(utilCpu); - this.utilCpu = utilCpu; + public CostResources getTargetUtilization() { + return targetUtilization; } - public double getUtilDisk() { - return utilDisk; + public CostResources getResultUtilization() { + return resultUtilization; } - public void setUtilDisk(float utilDisk) { - validateUtilRatio(utilDisk); - this.utilDisk = utilDisk; - } - - public double getUtilMem() { - return utilMem; - } - - public void setUtilMem(float utilMem) { - validateUsageRatio(utilMem); - this.utilMem = utilMem; - } - - public double getUsageDiskBusy() { - return usageDiskBusy; - } - - public void setUsageDiskBusy(float usageDiskBusy) { - validateUsageRatio(usageDiskBusy); - this.usageDiskBusy = usageDiskBusy; - } - - public double getUtilDiskBusy() { - return utilDiskBusy; - } - - public void setUtilDiskBusy(float utilDiskBusy) { - validateUtilRatio(utilDiskBusy); - this.utilDiskBusy = utilDiskBusy; - } + static CostResources calculateResultUtilization(CostResources system, CostResources target) { + double cpu = system.getCpu()/target.getCpu(); + double mem = system.getMemory()/target.getMemory(); + double disk = system.getDisk()/target.getDisk(); + double diskbusy = system.getDiskBusy()/target.getDiskBusy(); - private void validateUsageRatio(float ratio) { - if (ratio < 0) throw new IllegalArgumentException("Usage cannot be negative"); - if (ratio > 1) throw new IllegalArgumentException("Usage exceed 1 (using more than it has available)"); + return new CostResources(mem, cpu, disk, diskbusy); } - private void validateUtilRatio(float ratio) { - if (ratio < 0) throw new IllegalArgumentException("Utilization cannot be negative"); + static double ration(double a, double b) { + if (b == 0) return 1; + return a/b; } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostHardwareInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostHardwareInfo.java deleted file mode 100644 index 1035c081376..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostHardwareInfo.java +++ /dev/null @@ -1,39 +0,0 @@ -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. - -/** - * Value object for hardware resources. - * - * Can be for actual readings or target numbers. - * - * @author smorgrav - */ -public class CostHardwareInfo { - - private final double memoryGb; - private final double cpuCores; - private final double diskGb; - private final double diskBusyPercentage; - - public CostHardwareInfo(double memoryGb, double cpuCores, double diskGb, double diskBusyPercentage) { - this.memoryGb = memoryGb; - this.cpuCores = cpuCores; - this.diskGb = diskGb; - this.diskBusyPercentage = diskBusyPercentage; - } - - public double getMemoryGb() { - return memoryGb; - } - - public double getCpuCores() { - return cpuCores; - } - - public double getDiskGb() { - return diskGb; - } - - public double getDiskBusyPercentage() { - return diskBusyPercentage; - } -} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostResources.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostResources.java new file mode 100644 index 00000000000..44ce5098e9f --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/cost/CostResources.java @@ -0,0 +1,57 @@ +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. + +/** + * Value object for system resources as ratios of available resources. + * + * Can be for actual readings or target numbers. + * + * @author smorgrav + */ +public class CostResources { + + private final double memory; + private final double cpu; + private final double disk; + private final double diskBusy; + private final double maxUtilization; + + public CostResources(double memory, double cpu, double disk, double diskBusy) { + this.memory = memory; + this.cpu = cpu; + this.disk = disk; + this.diskBusy = diskBusy; + + double maxUtil = Math.max(cpu, disk); + maxUtil = Math.max(maxUtil, memory); + this.maxUtilization = Math.max(maxUtil, diskBusy); + } + + public double getMaxUtilization() { + return maxUtilization; + } + + public double getMemory() { + return memory; + } + + public double getCpu() { + return cpu; + } + + public double getDisk() { + return disk; + } + + public double getDiskBusy() { + return diskBusy; + } + + private void validateUsageRatio(float ratio) { + if (ratio < 0) throw new IllegalArgumentException("Usage cannot be negative"); + if (ratio > 1) throw new IllegalArgumentException("Usage exceed 1 (using more than it has available)"); + } + + private void validateUtilRatio(float ratio) { + if (ratio < 0) throw new IllegalArgumentException("Utilization cannot be negative"); + } +} 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 index f02f8582c52..2e963106129 100644 --- 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 @@ -19,9 +19,9 @@ public class CostJsonModelAdapter { public static CostJsonModel.Application toJsonModel(CostApplication appCost) { CostJsonModel.Application app = new CostJsonModel.Application(); app.zone = appCost.getZone().toString(); - app.tenant = appCost.getTenant(); - app.app = appCost.getApp(); - app.tco = appCost.getTco(); + app.tenant = appCost.getAppId().tenant().toString(); + app.app = appCost.getAppId().application().toString(); + app.tco = (int)appCost.getTco(); app.utilization = appCost.getUtilization(); app.waste = appCost.getWaste(); app.cluster = new HashMap<>(); @@ -37,7 +37,7 @@ public class CostJsonModelAdapter { object.setString("zone", appCost.getZone().toString()); object.setString("tenant", appCost.getTenant()); object.setString("app", appCost.getApp()); - object.setLong("tco", appCost.getTco()); + object.setLong("tco", (long)appCost.getTco()); object.setDouble("utilization", appCost.getUtilization()); object.setDouble("waste", appCost.getWaste()); Cursor clustersObject = object.setObject("cluster"); -- cgit v1.2.3