From 72c61694d5dd3b4649dfd71ee1b63c8b86e1f0dd Mon Sep 17 00:00:00 2001 From: Anthony Johnson <ansoni@gmail.com> Date: Wed, 25 Feb 2015 20:59:29 -0500 Subject: [PATCH 1/8] first pass for credits --- .../ice/basic/BasicLineItemProcessor.java | 85 ++++++++++++++++--- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java index f16a88dd..739cdf21 100644 --- a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java +++ b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java @@ -28,6 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -88,23 +89,73 @@ public long getEndMillis(String[] items) { } public Result process(long startMilli, boolean processDelayed, ProcessorConfig config, String[] items, Map<Product, ReadWriteData> usageDataByProduct, Map<Product, ReadWriteData> costDataByProduct, Map<String, Double> ondemandRate) { - if (StringUtils.isEmpty(items[accountIdIndex]) || - StringUtils.isEmpty(items[productIndex]) || - StringUtils.isEmpty(items[usageTypeIndex]) || - StringUtils.isEmpty(items[operationIndex]) || - StringUtils.isEmpty(items[usageQuantityIndex]) || - StringUtils.isEmpty(items[costIndex])) + + if (StringUtils.isEmpty(items[costIndex])) { + logger.debug("Ignoring Record due to missing Cost - " + Arrays.toString(items)); + return Result.ignore; + } + + double costValue = Double.parseDouble(items[costIndex]); + boolean credit = false; + + // make sure we don't ignore credits + if (costValue < 0) { + credit = true; + } + + //fail-fast on records we can't process + if (StringUtils.isEmpty(items[accountIdIndex])) { + logger.debug("Ignoring Record due to missing Account Id - " + Arrays.toString(items)); + return Result.ignore; + } else if (StringUtils.isEmpty(items[productIndex])) { + logger.debug("Ignoring Record due to missing Product - " + Arrays.toString(items)); return Result.ignore; + } + + // check other records. We might have to help some credit rows to show up properly + if (StringUtils.isEmpty(items[usageTypeIndex])) { + if (credit) { + items[usageTypeIndex]="credit"; + } else { + logger.debug("Ignoring Record due to missing Usage Type - " + Arrays.toString(items)); + return Result.ignore; + } + } + if (StringUtils.isEmpty(items[operationIndex])) { + if (credit) { + items[operationIndex]="credit"; + } else { + logger.debug("Ignoring Record due to missing Operation - " + Arrays.toString(items)); + return Result.ignore; + } + } + if (StringUtils.isEmpty(items[usageQuantityIndex])) { + if (credit) { + items[usageQuantityIndex]="1"; + } else { + logger.debug("Ignoring Record due to missing Usage Quantity - " + Arrays.toString(items)); + return Result.ignore; + } + } Account account = config.accountService.getAccountById(items[accountIdIndex]); if (account == null) return Result.ignore; double usageValue = Double.parseDouble(items[usageQuantityIndex]); - double costValue = Double.parseDouble(items[costIndex]); long millisStart; long millisEnd; + + boolean monthlyCredit=false; + Result result = Result.hourly; + if (credit && items[startTimeIndex].isEmpty()) { + items[startTimeIndex]=new DateTime(startMilli, DateTimeZone.UTC).toString(amazonBillingDateFormat); + int numHoursInMonth = new DateTime(startMilli, DateTimeZone.UTC).dayOfMonth().getMaximumValue() * 24; + items[endTimeIndex]=new DateTime(startMilli, DateTimeZone.UTC).plusHours(numHoursInMonth).toString(amazonBillingDateFormat); + monthlyCredit=true; + } + try { millisStart = amazonBillingDateFormat.parseMillis(items[startTimeIndex]); millisEnd = amazonBillingDateFormat.parseMillis(items[endTimeIndex]); @@ -116,7 +167,7 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c Product product = config.productService.getProductByAwsName(items[productIndex]); boolean reservationUsage = "Y".equals(items[reservedIndex]); - ReformedMetaData reformedMetaData = reform(millisStart, config, product, reservationUsage, items[operationIndex], items[usageTypeIndex], items[descriptionIndex], costValue); + ReformedMetaData reformedMetaData = reform(millisStart, config, product, reservationUsage, items[operationIndex], items[usageTypeIndex], items[descriptionIndex], costValue, credit); product = reformedMetaData.product; Operation operation = reformedMetaData.operation; UsageType usageType = reformedMetaData.usageType; @@ -125,7 +176,6 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c int startIndex = (int)((millisStart - startMilli)/ AwsUtils.hourMillis); int endIndex = (int)((millisEnd + 1000 - startMilli)/ AwsUtils.hourMillis); - Result result = Result.hourly; if (product == Product.ec2_instance) { result = processEc2Instance(processDelayed, reservationUsage, operation, zone); } @@ -145,13 +195,18 @@ else if (product == Product.rds) { result = processRds(usageType); } + // make sure credits for the month are distributed across month + if (result == Result.ignore || result == Result.delay) return result; if (usageType.name.startsWith("TimedStorage-ByteHrs")) result = Result.daily; - boolean monthlyCost = StringUtils.isEmpty(items[descriptionIndex]) ? false : items[descriptionIndex].toLowerCase().contains("-month"); + if (monthlyCredit) + result = Result.monthly; + + boolean monthlyCost = StringUtils.isEmpty(items[descriptionIndex]) ? false : ( items[descriptionIndex].toLowerCase().contains("-month") || monthlyCredit ); ReadWriteData usageData = usageDataByProduct.get(null); ReadWriteData costData = costDataByProduct.get(null); @@ -167,6 +222,8 @@ else if (product == Product.rds) { else if (result == Result.monthly) { startIndex = 0; endIndex = usageData.getNum(); + logger.debug("Start Index: " + startIndex); + logger.debug("End Index: " + endIndex); int numHoursInMonth = new DateTime(startMilli, DateTimeZone.UTC).dayOfMonth().getMaximumValue() * 24; usageValue = usageValue * endIndex / numHoursInMonth; costValue = costValue * endIndex / numHoursInMonth; @@ -234,6 +291,9 @@ else if (result == Result.monthly) { return result; for (int i : indexes) { + if (credit) { + logger.debug("Index: " + i); + } if (config.randomizer != null) { @@ -345,7 +405,7 @@ private Result processRds(UsageType usageType) { return Result.hourly; } - protected ReformedMetaData reform(long millisStart, ProcessorConfig config, Product product, boolean reservationUsage, String operationStr, String usageTypeStr, String description, double cost) { + protected ReformedMetaData reform(long millisStart, ProcessorConfig config, Product product, boolean reservationUsage, String operationStr, String usageTypeStr, String description, double cost, boolean credit) { Operation operation = null; UsageType usageType = null; @@ -432,6 +492,9 @@ else if (usageTypeStr.startsWith("HeavyUsage") || usageTypeStr.startsWith("Mediu usageType = UsageType.getUsageType(usageTypeStr, operation, description); } + if (credit) { + product = new Product(product.name + " credit"); + } return new ReformedMetaData(region, product, operation, usageType); } From e390873d05a8bc08d12652a712a08b7157542979 Mon Sep 17 00:00:00 2001 From: Anthony Johnson <ansoni@gmail.com> Date: Mon, 2 Mar 2015 15:31:45 -0500 Subject: [PATCH 2/8] -- re-work some of the credit handling -- update ice.js to not ignore negative Y axis -- ignored records now have a debug statement. --- .../ice/basic/BasicLineItemProcessor.java | 91 ++++---- web-app/js/ice.js | 216 ++++++++++++++++-- 2 files changed, 250 insertions(+), 57 deletions(-) diff --git a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java index 739cdf21..877a28f7 100644 --- a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java +++ b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java @@ -103,7 +103,11 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c credit = true; } - //fail-fast on records we can't process + if (credit) { + if (! reformCredit(startMilli, items)) + return Result.ignore; + } + // fail-fast on records we can't process if (StringUtils.isEmpty(items[accountIdIndex])) { logger.debug("Ignoring Record due to missing Account Id - " + Arrays.toString(items)); return Result.ignore; @@ -112,49 +116,32 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c return Result.ignore; } - // check other records. We might have to help some credit rows to show up properly if (StringUtils.isEmpty(items[usageTypeIndex])) { - if (credit) { - items[usageTypeIndex]="credit"; - } else { - logger.debug("Ignoring Record due to missing Usage Type - " + Arrays.toString(items)); - return Result.ignore; - } + logger.debug("Ignoring Record due to missing Usage Type - " + Arrays.toString(items)); + return Result.ignore; } if (StringUtils.isEmpty(items[operationIndex])) { - if (credit) { - items[operationIndex]="credit"; - } else { - logger.debug("Ignoring Record due to missing Operation - " + Arrays.toString(items)); - return Result.ignore; - } + logger.debug("Ignoring Record due to missing Operation - " + Arrays.toString(items)); + return Result.ignore; } if (StringUtils.isEmpty(items[usageQuantityIndex])) { - if (credit) { - items[usageQuantityIndex]="1"; - } else { - logger.debug("Ignoring Record due to missing Usage Quantity - " + Arrays.toString(items)); - return Result.ignore; - } + items[usageQuantityIndex]="1"; + logger.debug("Ignoring Record due to missing Usage Quantity - " + Arrays.toString(items)); + return Result.ignore; } Account account = config.accountService.getAccountById(items[accountIdIndex]); - if (account == null) + if (account == null) { + logger.debug("Ignoring Record due to missing Account - " + Arrays.toString(items)); return Result.ignore; + } double usageValue = Double.parseDouble(items[usageQuantityIndex]); long millisStart; long millisEnd; - boolean monthlyCredit=false; Result result = Result.hourly; - if (credit && items[startTimeIndex].isEmpty()) { - items[startTimeIndex]=new DateTime(startMilli, DateTimeZone.UTC).toString(amazonBillingDateFormat); - int numHoursInMonth = new DateTime(startMilli, DateTimeZone.UTC).dayOfMonth().getMaximumValue() * 24; - items[endTimeIndex]=new DateTime(startMilli, DateTimeZone.UTC).plusHours(numHoursInMonth).toString(amazonBillingDateFormat); - monthlyCredit=true; - } try { millisStart = amazonBillingDateFormat.parseMillis(items[startTimeIndex]); @@ -195,18 +182,15 @@ else if (product == Product.rds) { result = processRds(usageType); } - // make sure credits for the month are distributed across month - - if (result == Result.ignore || result == Result.delay) + if (result == Result.ignore || result == Result.delay) { + logger.debug("Record not processed - " + result + " - " + Arrays.toString(items)); return result; + } if (usageType.name.startsWith("TimedStorage-ByteHrs")) result = Result.daily; - if (monthlyCredit) - result = Result.monthly; - - boolean monthlyCost = StringUtils.isEmpty(items[descriptionIndex]) ? false : ( items[descriptionIndex].toLowerCase().contains("-month") || monthlyCredit ); + boolean monthlyCost = StringUtils.isEmpty(items[descriptionIndex]) ? false : ( items[descriptionIndex].toLowerCase().contains("-month") ); ReadWriteData usageData = usageDataByProduct.get(null); ReadWriteData costData = costDataByProduct.get(null); @@ -222,8 +206,6 @@ else if (product == Product.rds) { else if (result == Result.monthly) { startIndex = 0; endIndex = usageData.getNum(); - logger.debug("Start Index: " + startIndex); - logger.debug("End Index: " + endIndex); int numHoursInMonth = new DateTime(startMilli, DateTimeZone.UTC).dayOfMonth().getMaximumValue() * 24; usageValue = usageValue * endIndex / numHoursInMonth; costValue = costValue * endIndex / numHoursInMonth; @@ -291,10 +273,6 @@ else if (result == Result.monthly) { return result; for (int i : indexes) { - if (credit) { - logger.debug("Index: " + i); - } - if (config.randomizer != null) { if (tagGroup.product != Product.rds && tagGroup.product != Product.s3 && usageData.getData(i).get(tagGroup) != null) @@ -405,6 +383,37 @@ private Result processRds(UsageType usageType) { return Result.hourly; } + protected boolean reformCredit(long startMilli, String[] items) { + + String[] split_description = items[descriptionIndex].split(":"); + + // If the credit doesn't have a start and end time then we... set to end to end of month + if (items[startTimeIndex].isEmpty()) { + int numHoursInMonth = new DateTime(startMilli, DateTimeZone.UTC).dayOfMonth().getMaximumValue() * 24; + items[startTimeIndex]=new DateTime(startMilli, DateTimeZone.UTC).plusHours(numHoursInMonth-1).toString(amazonBillingDateFormat); + items[endTimeIndex]=new DateTime(startMilli, DateTimeZone.UTC).plusHours(numHoursInMonth).toString(amazonBillingDateFormat); + logger.debug("Credit did not have a Time - Set to " + items[startTimeIndex] + "-" + items[endTimeIndex]) ; + } + + // try to use the description to fetch some info about the credit if we have nothing + if (items[operationIndex].isEmpty()) { + if (split_description.length > 0) + items[operationIndex]=split_description[0]; + else + items[operationIndex]="credit"; + } + + if (items[usageTypeIndex].isEmpty()) { + items[usageTypeIndex]="credit"; + } + + if (items[usageQuantityIndex].isEmpty()) + items[usageQuantityIndex]="1"; + + return true; + + } + protected ReformedMetaData reform(long millisStart, ProcessorConfig config, Product product, boolean reservationUsage, String operationStr, String usageTypeStr, String description, double cost, boolean credit) { Operation operation = null; diff --git a/web-app/js/ice.js b/web-app/js/ice.js index f76e6011..22df2cdc 100644 --- a/web-app/js/ice.js +++ b/web-app/js/ice.js @@ -59,6 +59,7 @@ ice.factory('highchart', function() { plotOptions: { area: {lineWidth: 1, stacking: 'normal'}, column: {lineWidth: 1, stacking: 'normal'}, + line: {lineWidth: 1, stacking: null}, series: { states: { hover: { @@ -92,11 +93,11 @@ ice.factory('highchart', function() { var precision = currencySign === "" ? 0 : (currencySign === "¢" ? 4 : 2); for (var i = 0; i < this.points.length - (showsps ? 1 : 0); i++) { var point = this.points[i]; - if (i == 0) { + if (i == 0 && point.total!=undefined) { s += '<br/><span>aggregated : ' + currencySign + Highcharts.numberFormat(showsps ? total : point.total, precision, '.') + ' / ' + (factorsps ? metricunitname : consolidate); } var perc = showsps ? point.y * 100 / total : point.percentage; - s += '<br/><span style="color: ' + point.series.color + '">' + point.series.name + '</span> : ' + currencySign + Highcharts.numberFormat(point.y, precision, '.') + ' / ' + (factorsps ? metricunitname : consolidate) + ' (' + Highcharts.numberFormat(perc, 1) + '%)'; + s += '<br/><span style="color: ' + point.series.color + '">' + point.series.name + '</span> : ' + currencySign + Highcharts.numberFormat(point.y, precision, '.') + ' / ' + (factorsps ? metricunitname : consolidate) + ' (' + Highcharts.numberFormat(perc, 1) + '%)'; if (i > 40 && point) break; } @@ -106,7 +107,55 @@ ice.factory('highchart', function() { } }; - var setupHcData = function(result, plotType, showsps) { + var setupHcEstimate = function(result, plotType, cumulative) { + plotType = "line"; + Highcharts.setOptions({ + global: { + useUTC: true + } + }); + + var hadEstimate = false; + + for (i in result.estimates) { + var estimates = result.estimates[i].data; + var hasEstimate = false; + for (j in estimates) { + if (cumulative && cumulative == "true" && j > 0) { + estimates[j] = estimates[j-1] + parseFloat(estimates[j].toFixed(2)); + //aggregateEstimates[j] += estimates[j]; + } else { + estimates[j] = parseFloat(estimates[j].toFixed(2)); + //aggregateEstimates[j] += estimates[j]; + } + if (estimates[j] !== 0) + hasEstimate = true; + } + + if (hasEstimate) { + hadEstimate = true; + if (!result.interval && result.time) { + for (j in estimates) { + estimates[j] = [result.time[j], estimates[j]]; + } + } + + var serie = { + name: result.estimates[i].name + " Estimate", + data: estimates, + pointStart: result.start, + pointInterval: result.interval, + //step: true, + type: "line", + Index: -1 + }; + + hc_options.series.push(serie); + } + } + } + + var setupHcData = function(result, plotType, showsps, cumulative) { Highcharts.setOptions({ global: { @@ -120,7 +169,10 @@ ice.factory('highchart', function() { var data = result.data[i].data; var hasData = false; for (j in data) { - data[j] = parseFloat(data[j].toFixed(2)); + if (cumulative && cumulative == "true" && j > 0) + data[j] = data[j-1] + parseFloat(data[j].toFixed(2)); + else + data[j] = parseFloat(data[j].toFixed(2)); if (data[j] !== 0) hasData = true; } @@ -159,7 +211,7 @@ ice.factory('highchart', function() { } var setupYAxis = function(isCost, showsps, factorsps) { - var yAxis = {title:{text: (isCost ? 'Cost' : 'Usage') + " per " + (factorsps ? metricunitname : consolidate)}, min: 0, lineWidth: 2}; + var yAxis = {title:{text: (isCost ? 'Cost' : 'Usage') + " per " + (factorsps ? metricunitname : consolidate)}, lineWidth: 2}; if (isCost) yAxis.labels = { formatter: function() { @@ -169,7 +221,7 @@ ice.factory('highchart', function() { hc_options.yAxis = [yAxis]; if (showsps) { - hc_options.yAxis.push({title:{text:metricname}, height: 100, min: 0, lineWidth: 2, offset: 0}); + hc_options.yAxis.push({title:{text:metricname}, height: 100, lineWidth: 2, offset: 0}); hc_options.yAxis[0].top = 150; hc_options.yAxis[0].height = 350; } @@ -195,7 +247,8 @@ ice.factory('highchart', function() { currencySign = $scope.usage_cost === 'cost' ? ($scope.factorsps ? factoredCostCurrencySign : global_currencySign) : ""; hc_options.legend.enabled = legendEnabled; - setupHcData(result, $scope.plotType, $scope.showsps); + setupHcData(result, $scope.plotType, $scope.showsps, $scope.cumulative); + setupHcEstimate(result, $scope.plotType, $scope.cumulative); setupYAxis($scope.usage_cost === 'cost', $scope.showsps, $scope.factorsps); showsps = $scope.showsps; factorsps = $scope.factorsps; @@ -210,7 +263,7 @@ ice.factory('highchart', function() { var i = 0; for (i = 0; i < chart.series.length - ($scope.showsps ? 2 : 1); i++) { if ($scope && $scope.legends) { - var legend = { + var legend = { name: chart.series[i].name, style: "color: " + chart.series[i].color, iconStyle: "background-color: " + chart.series[i].color, @@ -365,7 +418,7 @@ ice.factory('usage_db', function($window, $http, $filter) { } $location.hash(result); - + if (time) { timeParams = time; } @@ -380,9 +433,9 @@ ice.factory('usage_db', function($window, $http, $filter) { if (hash) { var params = hash.split("&"); for (i = 0; i < params.length; i++) { - if (params[i].indexOf("=") < 0 && i > 0 && (params[i-1].indexOf("appgroup=") == 0 || params[i-1].indexOf("resourceGroup=") == 0)) - params[i-1] = params[i-1] + "&" + params[i]; - } + if (params[i].indexOf("=") < 0 && i > 0 && (params[i-1].indexOf("appgroup=") == 0 || params[i-1].indexOf("resourceGroup=") == 0)) + params[i-1] = params[i-1] + "&" + params[i]; + } var i, j, time = ""; for (i = 0; i < params.length; i++) { @@ -458,6 +511,9 @@ ice.factory('usage_db', function($window, $http, $filter) { else if (params[i].indexOf("resourceGroup=") === 0) { $scope.selected__resourceGroups = params[i].substr(14).split(","); } + else if (params[i].indexOf("includeEstimates=") === 0) { + $scope.includeEstimates = "true" === params[i].substr(17); + } } } if (!$scope.showResourceGroups) { @@ -687,7 +743,7 @@ ice.factory('usage_db', function($window, $http, $filter) { showsps: $scope.showsps ? true : false, factorsps: $scope.factorsps ? true : false }, params); - this.addParams(params, "account", $scope.accounts, $scope.selected_accounts, $scope.selected__accounts, $scope.filter_accounts); + this.addParams(params, "account", $scope.accounts, $scope.selected__accounts, $scope.filter_accounts); if ($scope.showZones) this.addParams(params, "zone", $scope.zones, $scope.selected_zones, $scope.selected__zones, $scope.filter_zones); else @@ -1170,6 +1226,135 @@ function detailCtrl($scope, $location, $http, usage_db, highchart) { fn(); } +function estimateCtrl($scope, $location, $http, usage_db, highchart) { + + $scope.graphType = "aggregate"; + $scope.legends = []; + $scope.usage_cost = "cost"; + $scope.includeEstimates=true; + $scope.cumulative="true"; + $scope.dailyEstimate=500; + $scope.groupBy={ name: "Account" } + $scope.consolidate = "daily"; + $scope.end = new Date(); + $scope.start = new Date(); + $scope.plotType = "area"; + + var startMonth = $scope.end.getUTCMonth() - 1; + var startYear = $scope.end.getUTCFullYear(); + if (startMonth < 0) { + startMonth += 12; + startYear -= 1; + } + $scope.start.setUTCFullYear(startYear); + $scope.start.setUTCMonth(startMonth); + $scope.start.setUTCDate(1); + $scope.start.setUTCHours(0); + + $scope.end = highchart.dateFormat($scope.end); //$filter('date')($scope.end, "y-MM-dd hha"); + $scope.start = highchart.dateFormat($scope.start); //$filter('date')($scope.start, "y-MM-dd hha"); + + + $scope.updateUrl = function() { + $scope.end = jQuery('#end').datetimepicker().val(); + $scope.start = jQuery('#start').datetimepicker().val(); + + var params = { + account: {selected: $scope.selected_accounts, from: $scope.accounts}, + usage_cost: $scope.usage_cost, + start: $scope.start, + end: $scope.end, + groupBy: "Account", + plotType: "area", + aggregate: "none", + consolidate: $scope.consolidate, + graphType: $scope.graphType, + includeEstimates: "" + $scope.includeEstimates + }; + usage_db.updateUrl($location, params); + } + + $scope.download = function() { + usage_db.getData($scope, null, null, true); + } + + $scope.getData = function() { + $scope.loading = true; + usage_db.getData($scope, function(result){ + var hourlydata = []; + var estimatedata = []; + for (var key in result.data) { + hourlydata.push({name: key, data: result.data[key]}); + } + for (var key in result.estimates) { + estimatedata.push({name: key, data: result.estimates[key]}); + } + result.data = hourlydata; + result.estimates = estimatedata; + $scope.legends = []; + $scope.stats = result.stats; + highchart.drawGraph(result, $scope); + + $scope.legendName = $scope.groupBy.name; + $scope.legend_usage_cost = $scope.usage_cost; + }, { includeEstimates: "" + $scope.includeEstimates}); + } + + $scope.accountsChanged = function() { + $scope.updateEstimates(); + } + + $scope.updateEstimates = function() { + usage_db.getDailyEstimate($scope); + } + + $scope.updateUsageTypes = function() { + usage_db.getUsageTypes($scope, function(data){ + }); + } + + $scope.removeEstimatesFilter = function(legend) { + return legend.name.indexOf("Estimate") == -1; + }; + + + var fn = function() { + usage_db.getAccounts($scope, function(data){ + //while (! $scope.selected_account) { + //we need to have these! + // } + }); + $scope.getData(); + + jQuery("#start, #end" ).datetimepicker({ + showTime: false, + showMinute: false, + ampm: true, + timeFormat: 'hhTT', + dateFormat: 'yy-mm-dd' + }); + jQuery('#end').datetimepicker().val($scope.end); + jQuery('#start').datetimepicker().val($scope.start); + } + + usage_db.getParams($location.hash(), $scope); + + if ($scope.spans) { + $http({ + method: "GET", + url: "getTimeSpan", + params: {spans: $scope.spans, end: $scope.end, consolidate: $scope.consolidate} + }).success(function(result) { + $scope.end = result.end; + $scope.start = result.start; + fn(); + }); + } else { + fn(); + } +} + + function appgroupCtrl($scope, $location, $http, usage_db, highchart) { // var predefinedQuery = {product: "ebs,ec2,ec2_instance,monitor,rds,s3"}; @@ -1496,7 +1681,7 @@ function summaryCtrl($scope, $location, usage_db, highchart) { {name: "UsageType"} ], $scope.groupBy = $scope.groupBys[2]; - $scope.consolidate = "hourly"; + $scope.consolidate = "monthly"; $scope.plotType = "area"; $scope.end = new Date(); $scope.start = new Date(); @@ -1526,7 +1711,7 @@ function summaryCtrl($scope, $location, usage_db, highchart) { $scope.order = function(index) { if ($scope.predicate != index) { - $scope.reservse = index === 'name'; + $scope.reserve = index === 'name'; $scope.predicate = index; } else { @@ -1804,4 +1989,3 @@ function editCtrl($scope, $location, $http) { } }); } - From 33386d86832d3bc8394ac52bbaf744697a582774 Mon Sep 17 00:00:00 2001 From: Anthony Johnson <ansoni@gmail.com> Date: Mon, 2 Mar 2015 15:51:12 -0500 Subject: [PATCH 3/8] Revert accidental modifications pulled in from another feature branch --- web-app/js/ice.js | 212 +++------------------------------------------- 1 file changed, 14 insertions(+), 198 deletions(-) diff --git a/web-app/js/ice.js b/web-app/js/ice.js index 22df2cdc..9daaac01 100644 --- a/web-app/js/ice.js +++ b/web-app/js/ice.js @@ -59,7 +59,6 @@ ice.factory('highchart', function() { plotOptions: { area: {lineWidth: 1, stacking: 'normal'}, column: {lineWidth: 1, stacking: 'normal'}, - line: {lineWidth: 1, stacking: null}, series: { states: { hover: { @@ -93,11 +92,11 @@ ice.factory('highchart', function() { var precision = currencySign === "" ? 0 : (currencySign === "¢" ? 4 : 2); for (var i = 0; i < this.points.length - (showsps ? 1 : 0); i++) { var point = this.points[i]; - if (i == 0 && point.total!=undefined) { + if (i == 0) { s += '<br/><span>aggregated : ' + currencySign + Highcharts.numberFormat(showsps ? total : point.total, precision, '.') + ' / ' + (factorsps ? metricunitname : consolidate); } var perc = showsps ? point.y * 100 / total : point.percentage; - s += '<br/><span style="color: ' + point.series.color + '">' + point.series.name + '</span> : ' + currencySign + Highcharts.numberFormat(point.y, precision, '.') + ' / ' + (factorsps ? metricunitname : consolidate) + ' (' + Highcharts.numberFormat(perc, 1) + '%)'; + s += '<br/><span style="color: ' + point.series.color + '">' + point.series.name + '</span> : ' + currencySign + Highcharts.numberFormat(point.y, precision, '.') + ' / ' + (factorsps ? metricunitname : consolidate) + ' (' + Highcharts.numberFormat(perc, 1) + '%)'; if (i > 40 && point) break; } @@ -107,55 +106,7 @@ ice.factory('highchart', function() { } }; - var setupHcEstimate = function(result, plotType, cumulative) { - plotType = "line"; - Highcharts.setOptions({ - global: { - useUTC: true - } - }); - - var hadEstimate = false; - - for (i in result.estimates) { - var estimates = result.estimates[i].data; - var hasEstimate = false; - for (j in estimates) { - if (cumulative && cumulative == "true" && j > 0) { - estimates[j] = estimates[j-1] + parseFloat(estimates[j].toFixed(2)); - //aggregateEstimates[j] += estimates[j]; - } else { - estimates[j] = parseFloat(estimates[j].toFixed(2)); - //aggregateEstimates[j] += estimates[j]; - } - if (estimates[j] !== 0) - hasEstimate = true; - } - - if (hasEstimate) { - hadEstimate = true; - if (!result.interval && result.time) { - for (j in estimates) { - estimates[j] = [result.time[j], estimates[j]]; - } - } - - var serie = { - name: result.estimates[i].name + " Estimate", - data: estimates, - pointStart: result.start, - pointInterval: result.interval, - //step: true, - type: "line", - Index: -1 - }; - - hc_options.series.push(serie); - } - } - } - - var setupHcData = function(result, plotType, showsps, cumulative) { + var setupHcData = function(result, plotType, showsps) { Highcharts.setOptions({ global: { @@ -169,10 +120,7 @@ ice.factory('highchart', function() { var data = result.data[i].data; var hasData = false; for (j in data) { - if (cumulative && cumulative == "true" && j > 0) - data[j] = data[j-1] + parseFloat(data[j].toFixed(2)); - else - data[j] = parseFloat(data[j].toFixed(2)); + data[j] = parseFloat(data[j].toFixed(2)); if (data[j] !== 0) hasData = true; } @@ -247,8 +195,7 @@ ice.factory('highchart', function() { currencySign = $scope.usage_cost === 'cost' ? ($scope.factorsps ? factoredCostCurrencySign : global_currencySign) : ""; hc_options.legend.enabled = legendEnabled; - setupHcData(result, $scope.plotType, $scope.showsps, $scope.cumulative); - setupHcEstimate(result, $scope.plotType, $scope.cumulative); + setupHcData(result, $scope.plotType, $scope.showsps); setupYAxis($scope.usage_cost === 'cost', $scope.showsps, $scope.factorsps); showsps = $scope.showsps; factorsps = $scope.factorsps; @@ -263,7 +210,7 @@ ice.factory('highchart', function() { var i = 0; for (i = 0; i < chart.series.length - ($scope.showsps ? 2 : 1); i++) { if ($scope && $scope.legends) { - var legend = { + var legend = { name: chart.series[i].name, style: "color: " + chart.series[i].color, iconStyle: "background-color: " + chart.series[i].color, @@ -418,7 +365,7 @@ ice.factory('usage_db', function($window, $http, $filter) { } $location.hash(result); - + if (time) { timeParams = time; } @@ -433,9 +380,9 @@ ice.factory('usage_db', function($window, $http, $filter) { if (hash) { var params = hash.split("&"); for (i = 0; i < params.length; i++) { - if (params[i].indexOf("=") < 0 && i > 0 && (params[i-1].indexOf("appgroup=") == 0 || params[i-1].indexOf("resourceGroup=") == 0)) - params[i-1] = params[i-1] + "&" + params[i]; - } + if (params[i].indexOf("=") < 0 && i > 0 && (params[i-1].indexOf("appgroup=") == 0 || params[i-1].indexOf("resourceGroup=") == 0)) + params[i-1] = params[i-1] + "&" + params[i]; + } var i, j, time = ""; for (i = 0; i < params.length; i++) { @@ -511,9 +458,6 @@ ice.factory('usage_db', function($window, $http, $filter) { else if (params[i].indexOf("resourceGroup=") === 0) { $scope.selected__resourceGroups = params[i].substr(14).split(","); } - else if (params[i].indexOf("includeEstimates=") === 0) { - $scope.includeEstimates = "true" === params[i].substr(17); - } } } if (!$scope.showResourceGroups) { @@ -743,7 +687,7 @@ ice.factory('usage_db', function($window, $http, $filter) { showsps: $scope.showsps ? true : false, factorsps: $scope.factorsps ? true : false }, params); - this.addParams(params, "account", $scope.accounts, $scope.selected__accounts, $scope.filter_accounts); + this.addParams(params, "account", $scope.accounts, $scope.selected_accounts, $scope.selected__accounts, $scope.filter_accounts); if ($scope.showZones) this.addParams(params, "zone", $scope.zones, $scope.selected_zones, $scope.selected__zones, $scope.filter_zones); else @@ -1226,135 +1170,6 @@ function detailCtrl($scope, $location, $http, usage_db, highchart) { fn(); } -function estimateCtrl($scope, $location, $http, usage_db, highchart) { - - $scope.graphType = "aggregate"; - $scope.legends = []; - $scope.usage_cost = "cost"; - $scope.includeEstimates=true; - $scope.cumulative="true"; - $scope.dailyEstimate=500; - $scope.groupBy={ name: "Account" } - $scope.consolidate = "daily"; - $scope.end = new Date(); - $scope.start = new Date(); - $scope.plotType = "area"; - - var startMonth = $scope.end.getUTCMonth() - 1; - var startYear = $scope.end.getUTCFullYear(); - if (startMonth < 0) { - startMonth += 12; - startYear -= 1; - } - $scope.start.setUTCFullYear(startYear); - $scope.start.setUTCMonth(startMonth); - $scope.start.setUTCDate(1); - $scope.start.setUTCHours(0); - - $scope.end = highchart.dateFormat($scope.end); //$filter('date')($scope.end, "y-MM-dd hha"); - $scope.start = highchart.dateFormat($scope.start); //$filter('date')($scope.start, "y-MM-dd hha"); - - - $scope.updateUrl = function() { - $scope.end = jQuery('#end').datetimepicker().val(); - $scope.start = jQuery('#start').datetimepicker().val(); - - var params = { - account: {selected: $scope.selected_accounts, from: $scope.accounts}, - usage_cost: $scope.usage_cost, - start: $scope.start, - end: $scope.end, - groupBy: "Account", - plotType: "area", - aggregate: "none", - consolidate: $scope.consolidate, - graphType: $scope.graphType, - includeEstimates: "" + $scope.includeEstimates - }; - usage_db.updateUrl($location, params); - } - - $scope.download = function() { - usage_db.getData($scope, null, null, true); - } - - $scope.getData = function() { - $scope.loading = true; - usage_db.getData($scope, function(result){ - var hourlydata = []; - var estimatedata = []; - for (var key in result.data) { - hourlydata.push({name: key, data: result.data[key]}); - } - for (var key in result.estimates) { - estimatedata.push({name: key, data: result.estimates[key]}); - } - result.data = hourlydata; - result.estimates = estimatedata; - $scope.legends = []; - $scope.stats = result.stats; - highchart.drawGraph(result, $scope); - - $scope.legendName = $scope.groupBy.name; - $scope.legend_usage_cost = $scope.usage_cost; - }, { includeEstimates: "" + $scope.includeEstimates}); - } - - $scope.accountsChanged = function() { - $scope.updateEstimates(); - } - - $scope.updateEstimates = function() { - usage_db.getDailyEstimate($scope); - } - - $scope.updateUsageTypes = function() { - usage_db.getUsageTypes($scope, function(data){ - }); - } - - $scope.removeEstimatesFilter = function(legend) { - return legend.name.indexOf("Estimate") == -1; - }; - - - var fn = function() { - usage_db.getAccounts($scope, function(data){ - //while (! $scope.selected_account) { - //we need to have these! - // } - }); - $scope.getData(); - - jQuery("#start, #end" ).datetimepicker({ - showTime: false, - showMinute: false, - ampm: true, - timeFormat: 'hhTT', - dateFormat: 'yy-mm-dd' - }); - jQuery('#end').datetimepicker().val($scope.end); - jQuery('#start').datetimepicker().val($scope.start); - } - - usage_db.getParams($location.hash(), $scope); - - if ($scope.spans) { - $http({ - method: "GET", - url: "getTimeSpan", - params: {spans: $scope.spans, end: $scope.end, consolidate: $scope.consolidate} - }).success(function(result) { - $scope.end = result.end; - $scope.start = result.start; - fn(); - }); - } else { - fn(); - } -} - - function appgroupCtrl($scope, $location, $http, usage_db, highchart) { // var predefinedQuery = {product: "ebs,ec2,ec2_instance,monitor,rds,s3"}; @@ -1681,7 +1496,7 @@ function summaryCtrl($scope, $location, usage_db, highchart) { {name: "UsageType"} ], $scope.groupBy = $scope.groupBys[2]; - $scope.consolidate = "monthly"; + $scope.consolidate = "hourly"; $scope.plotType = "area"; $scope.end = new Date(); $scope.start = new Date(); @@ -1711,7 +1526,7 @@ function summaryCtrl($scope, $location, usage_db, highchart) { $scope.order = function(index) { if ($scope.predicate != index) { - $scope.reserve = index === 'name'; + $scope.reservse = index === 'name'; $scope.predicate = index; } else { @@ -1989,3 +1804,4 @@ function editCtrl($scope, $location, $http) { } }); } + From 30e466c029f44ce98613b1477ceb84d6b9484b95 Mon Sep 17 00:00:00 2001 From: Anthony Johnson <ansoni@gmail.com> Date: Mon, 2 Mar 2015 15:57:52 -0500 Subject: [PATCH 4/8] small cleanup --- src/java/com/netflix/ice/basic/BasicLineItemProcessor.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java index 877a28f7..c9091967 100644 --- a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java +++ b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java @@ -111,11 +111,11 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c if (StringUtils.isEmpty(items[accountIdIndex])) { logger.debug("Ignoring Record due to missing Account Id - " + Arrays.toString(items)); return Result.ignore; - } else if (StringUtils.isEmpty(items[productIndex])) { + } + if (StringUtils.isEmpty(items[productIndex])) { logger.debug("Ignoring Record due to missing Product - " + Arrays.toString(items)); return Result.ignore; } - if (StringUtils.isEmpty(items[usageTypeIndex])) { logger.debug("Ignoring Record due to missing Usage Type - " + Arrays.toString(items)); return Result.ignore; @@ -125,7 +125,6 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c return Result.ignore; } if (StringUtils.isEmpty(items[usageQuantityIndex])) { - items[usageQuantityIndex]="1"; logger.debug("Ignoring Record due to missing Usage Quantity - " + Arrays.toString(items)); return Result.ignore; } From 89aba5e971c88f322db266ab82906de53f400ea9 Mon Sep 17 00:00:00 2001 From: Anthony Johnson <ansoni@gmail.com> Date: Thu, 9 Apr 2015 15:19:58 -0400 Subject: [PATCH 5/8] Updates to credit processing to ensure we capture all credit entries properly. --- .../ice/basic/BasicLineItemProcessor.java | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java index c9091967..98a425a6 100644 --- a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java +++ b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java @@ -101,12 +101,12 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c // make sure we don't ignore credits if (costValue < 0) { credit = true; - } - - if (credit) { - if (! reformCredit(startMilli, items)) + if (! reformCredit(startMilli, items)) { return Result.ignore; + } + logger.debug("Found Credit - " + Arrays.toString(items)); } + // fail-fast on records we can't process if (StringUtils.isEmpty(items[accountIdIndex])) { logger.debug("Ignoring Record due to missing Account Id - " + Arrays.toString(items)); @@ -251,7 +251,7 @@ else if (result == Result.monthly) { } catch (Exception e) { logger.error("failed to get RI price for " + tagGroup.region + " " + usageTypeForPrice); - resourceCostValue = -1; + resourceCostValue = Double.MIN_VALUE; } } @@ -272,6 +272,9 @@ else if (result == Result.monthly) { return result; for (int i : indexes) { + if (credit) { + logger.debug("Handled Credit Index " + i + " - " + costValue); + } if (config.randomizer != null) { if (tagGroup.product != Product.rds && tagGroup.product != Product.s3 && usageData.getData(i).get(tagGroup) != null) @@ -285,8 +288,8 @@ else if (result == Result.monthly) { Map<TagGroup, Double> usages = usageData.getData(i); Map<TagGroup, Double> costs = costData.getData(i); - addValue(usages, tagGroup, usageValue, config.randomizer == null || tagGroup.product == Product.rds || tagGroup.product == Product.s3); - addValue(costs, tagGroup, costValue, config.randomizer == null || tagGroup.product == Product.rds || tagGroup.product == Product.s3); + addValue(usages, tagGroup, usageValue, config.randomizer == null || tagGroup.product == Product.rds || tagGroup.product == Product.s3 || credit == true); + addValue(costs, tagGroup, costValue, config.randomizer == null || tagGroup.product == Product.rds || tagGroup.product == Product.s3 || credit == true); } else { resourceCostValue = usageValue * config.costPerMonitorMetricPerHour; @@ -296,9 +299,9 @@ else if (result == Result.monthly) { Map<TagGroup, Double> usagesOfResource = usageDataOfProduct.getData(i); Map<TagGroup, Double> costsOfResource = costDataOfProduct.getData(i); - if (config.randomizer == null || tagGroup.product == Product.rds || tagGroup.product == Product.s3) { + if (config.randomizer == null || tagGroup.product == Product.rds || tagGroup.product == Product.s3 || credit == true) { addValue(usagesOfResource, resourceTagGroup, usageValue, product != Product.monitor); - if (!config.useCostForResourceGroup.equals("modeled") || resourceCostValue < 0) { + if (!config.useCostForResourceGroup.equals("modeled") || resourceCostValue == Double.MIN_VALUE) { addValue(costsOfResource, resourceTagGroup, costValue, product != Product.monitor); } else { addValue(costsOfResource, resourceTagGroup, resourceCostValue, product != Product.monitor); @@ -394,6 +397,9 @@ protected boolean reformCredit(long startMilli, String[] items) { logger.debug("Credit did not have a Time - Set to " + items[startTimeIndex] + "-" + items[endTimeIndex]) ; } + // seperate credits into their own product for easy filtering/aggregation + items[productIndex]+=" credit"; + // try to use the description to fetch some info about the credit if we have nothing if (items[operationIndex].isEmpty()) { if (split_description.length > 0) @@ -406,8 +412,9 @@ protected boolean reformCredit(long startMilli, String[] items) { items[usageTypeIndex]="credit"; } - if (items[usageQuantityIndex].isEmpty()) - items[usageQuantityIndex]="1"; + if (items[usageQuantityIndex].isEmpty()) { + items[usageQuantityIndex] = "1"; + } return true; @@ -500,9 +507,10 @@ else if (usageTypeStr.startsWith("HeavyUsage") || usageTypeStr.startsWith("Mediu usageType = UsageType.getUsageType(usageTypeStr, operation, description); } - if (credit) { + // This method resets product. Make sure we seperated out our credits + if (credit && ! product.name.endsWith("credit")) product = new Product(product.name + " credit"); - } + return new ReformedMetaData(region, product, operation, usageType); } From 3c315c0b7269ef52f7638e3f46910fdb849067be Mon Sep 17 00:00:00 2001 From: Anthony Johnson <ansoni@gmail.com> Date: Mon, 13 Jul 2015 14:30:03 -0400 Subject: [PATCH 6/8] Add Support to turn Credit Support On/Off --- src/java/com/netflix/ice/basic/BasicLineItemProcessor.java | 2 +- src/java/com/netflix/ice/common/IceOptions.java | 5 +++++ src/java/com/netflix/ice/processor/ProcessorConfig.java | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java index 98a425a6..0392071b 100644 --- a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java +++ b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java @@ -101,7 +101,7 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c // make sure we don't ignore credits if (costValue < 0) { credit = true; - if (! reformCredit(startMilli, items)) { + if (config.ignoreCredits || ! reformCredit(startMilli, items)) { return Result.ignore; } logger.debug("Found Credit - " + Arrays.toString(items)); diff --git a/src/java/com/netflix/ice/common/IceOptions.java b/src/java/com/netflix/ice/common/IceOptions.java index c7d9bca9..0bf10f92 100644 --- a/src/java/com/netflix/ice/common/IceOptions.java +++ b/src/java/com/netflix/ice/common/IceOptions.java @@ -100,6 +100,11 @@ public class IceOptions { */ public static final String MONTHLY_CACHE_SIZE = "ice.monthlycachesize"; + /** + * Should we ignore credits or not? + */ + public static final String IGNORE_CREDITS = "ice.ignore_credits"; + /** * Cost per monitor metric per hour, It's optional. */ diff --git a/src/java/com/netflix/ice/processor/ProcessorConfig.java b/src/java/com/netflix/ice/processor/ProcessorConfig.java index c009dc4e..1fda642b 100644 --- a/src/java/com/netflix/ice/processor/ProcessorConfig.java +++ b/src/java/com/netflix/ice/processor/ProcessorConfig.java @@ -41,6 +41,7 @@ public class ProcessorConfig extends Config { public final LineItemProcessor lineItemProcessor; public final Randomizer randomizer; public final double costPerMonitorMetricPerHour; + public final boolean ignoreCredits; public final String useCostForResourceGroup; @@ -87,6 +88,7 @@ public ProcessorConfig( customTags = properties.getProperty(IceOptions.CUSTOM_TAGS, "").split(","); useCostForResourceGroup = properties.getProperty(IceOptions.RESOURCE_GROUP_COST, "modeled"); + ignoreCredits = Boolean.parseBoolean(properties.getProperty(IceOptions.IGNORE_CREDITS, "true")); ProcessorConfig.instance = this; From 6283e941da3554e8539109b259288ebdec195b8b Mon Sep 17 00:00:00 2001 From: Anthony Johnson <ansoni@gmail.com> Date: Mon, 13 Jul 2015 17:39:30 -0400 Subject: [PATCH 7/8] Move debug to info for ignored and credit line-items --- .../ice/basic/BasicLineItemProcessor.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java index 0392071b..d1a0cede 100644 --- a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java +++ b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java @@ -91,7 +91,7 @@ public long getEndMillis(String[] items) { public Result process(long startMilli, boolean processDelayed, ProcessorConfig config, String[] items, Map<Product, ReadWriteData> usageDataByProduct, Map<Product, ReadWriteData> costDataByProduct, Map<String, Double> ondemandRate) { if (StringUtils.isEmpty(items[costIndex])) { - logger.debug("Ignoring Record due to missing Cost - " + Arrays.toString(items)); + logger.info("Ignoring Record due to missing Cost - " + Arrays.toString(items)); return Result.ignore; } @@ -104,34 +104,34 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c if (config.ignoreCredits || ! reformCredit(startMilli, items)) { return Result.ignore; } - logger.debug("Found Credit - " + Arrays.toString(items)); + logger.info("Found Credit - " + Arrays.toString(items)); } // fail-fast on records we can't process if (StringUtils.isEmpty(items[accountIdIndex])) { - logger.debug("Ignoring Record due to missing Account Id - " + Arrays.toString(items)); + logger.info("Ignoring Record due to missing Account Id - " + Arrays.toString(items)); return Result.ignore; } if (StringUtils.isEmpty(items[productIndex])) { - logger.debug("Ignoring Record due to missing Product - " + Arrays.toString(items)); + logger.info("Ignoring Record due to missing Product - " + Arrays.toString(items)); return Result.ignore; } if (StringUtils.isEmpty(items[usageTypeIndex])) { - logger.debug("Ignoring Record due to missing Usage Type - " + Arrays.toString(items)); + logger.info("Ignoring Record due to missing Usage Type - " + Arrays.toString(items)); return Result.ignore; } if (StringUtils.isEmpty(items[operationIndex])) { - logger.debug("Ignoring Record due to missing Operation - " + Arrays.toString(items)); + logger.info("Ignoring Record due to missing Operation - " + Arrays.toString(items)); return Result.ignore; } if (StringUtils.isEmpty(items[usageQuantityIndex])) { - logger.debug("Ignoring Record due to missing Usage Quantity - " + Arrays.toString(items)); + logger.info("Ignoring Record due to missing Usage Quantity - " + Arrays.toString(items)); return Result.ignore; } Account account = config.accountService.getAccountById(items[accountIdIndex]); if (account == null) { - logger.debug("Ignoring Record due to missing Account - " + Arrays.toString(items)); + logger.info("Ignoring Record due to missing Account - " + Arrays.toString(items)); return Result.ignore; } @@ -182,7 +182,7 @@ else if (product == Product.rds) { } if (result == Result.ignore || result == Result.delay) { - logger.debug("Record not processed - " + result + " - " + Arrays.toString(items)); + logger.info("Record not processed - " + result + " - " + Arrays.toString(items)); return result; } From de1e9f2b3cc61e92499a1529ed2ee4e6e4caf2c9 Mon Sep 17 00:00:00 2001 From: Anthony Johnson <ansoni@gmail.com> Date: Wed, 21 Oct 2015 08:05:09 -0400 Subject: [PATCH 8/8] Add code to constrain credits to within a month --- grails-app/conf/BootStrap.groovy | 2 ++ src/java/com/netflix/ice/basic/BasicLineItemProcessor.java | 5 +++++ src/java/com/netflix/ice/common/IceOptions.java | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/grails-app/conf/BootStrap.groovy b/grails-app/conf/BootStrap.groovy index 44e978bf..796bf5b7 100644 --- a/grails-app/conf/BootStrap.groovy +++ b/grails-app/conf/BootStrap.groovy @@ -162,6 +162,8 @@ class BootStrap { properties.setProperty(IceOptions.ONDEMAND_COST_ALERT_THRESHOLD, prop.getProperty(IceOptions.ONDEMAND_COST_ALERT_THRESHOLD)); if (prop.getProperty(IceOptions.URL_PREFIX) != null) properties.setProperty(IceOptions.URL_PREFIX, prop.getProperty(IceOptions.URL_PREFIX)); + if (prop.getProperty(IceOptions.IGNORE_CREDITS) != null) + properties.setProperty(IceOptions.IGNORE_CREDITS, prop.getProperty(IceOptions.IGNORE_CREDITS)); ReservationCapacityPoller reservationCapacityPoller = null; if ("true".equals(prop.getProperty("ice.reservationCapacityPoller"))) { diff --git a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java index d1a0cede..76187ca7 100644 --- a/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java +++ b/src/java/com/netflix/ice/basic/BasicLineItemProcessor.java @@ -102,6 +102,7 @@ public Result process(long startMilli, boolean processDelayed, ProcessorConfig c if (costValue < 0) { credit = true; if (config.ignoreCredits || ! reformCredit(startMilli, items)) { + logger.info("Ignoring Credit - " + config.ignoreCredits); return Result.ignore; } logger.info("Found Credit - " + Arrays.toString(items)); @@ -208,6 +209,10 @@ else if (result == Result.monthly) { int numHoursInMonth = new DateTime(startMilli, DateTimeZone.UTC).dayOfMonth().getMaximumValue() * 24; usageValue = usageValue * endIndex / numHoursInMonth; costValue = costValue * endIndex / numHoursInMonth; + } else { + int maxEndIndex = usageData.getNum(); + if (endIndex > maxEndIndex) + endIndex = maxEndIndex; } if (monthlyCost) { diff --git a/src/java/com/netflix/ice/common/IceOptions.java b/src/java/com/netflix/ice/common/IceOptions.java index 0bf10f92..45961920 100644 --- a/src/java/com/netflix/ice/common/IceOptions.java +++ b/src/java/com/netflix/ice/common/IceOptions.java @@ -103,7 +103,7 @@ public class IceOptions { /** * Should we ignore credits or not? */ - public static final String IGNORE_CREDITS = "ice.ignore_credits"; + public static final String IGNORE_CREDITS = "ice.ignoreCredits"; /** * Cost per monitor metric per hour, It's optional.