diff --git a/src/main/java/com/autotune/analyzer/recommendations/engine/RecommendationEngine.java b/src/main/java/com/autotune/analyzer/recommendations/engine/RecommendationEngine.java index ebb74fe7c..a52bd1bc7 100644 --- a/src/main/java/com/autotune/analyzer/recommendations/engine/RecommendationEngine.java +++ b/src/main/java/com/autotune/analyzer/recommendations/engine/RecommendationEngine.java @@ -35,7 +35,10 @@ import com.autotune.utils.KruizeConstants; import com.autotune.utils.MetricsConfig; import com.autotune.utils.Utils; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import io.micrometer.core.instrument.Timer; import org.json.JSONObject; import org.slf4j.Logger; @@ -100,6 +103,26 @@ private static int getNumPods(Map filteredResultsMap private static void getPromQls(Map promQls) { } + /** + * Calculates the number of pods for a namespace based on the provided results map. + * + * @param filteredResultsMap A map containing timestamp as keys and contains metric results for the corresponding timestamp. + * @return int maximum number of pods observed across all timestamps in the filtered results map. + */ + private static int getNumPodsForNamespace(Map filteredResultsMap) { + LOGGER.debug("Size of Filter Map: {}", filteredResultsMap.size()); + Double max_pods_cpu = filteredResultsMap.values() + .stream() + .map(e -> { + Optional numPodsResults = Optional.ofNullable(e.getMetricResultsMap().get(AnalyzerConstants.MetricName.namespaceTotalPods)); + double numPods = numPodsResults.map(m -> m.getAggregationInfoResult().getAvg()).orElse(0.0); + return numPods; + }) + .max(Double::compareTo).get(); + + return (int) Math.ceil(max_pods_cpu); + } + private void init() { // Add new models recommendationModels = new ArrayList<>(); @@ -236,7 +259,7 @@ public String validate_local() { //TODO Instead of relying on the 'lo * @param calCount The count of incoming requests. * @return The KruizeObject containing the prepared recommendations. */ - public KruizeObject prepareRecommendations(int calCount) throws FetchMetricsError{ + public KruizeObject prepareRecommendations(int calCount) throws FetchMetricsError { Map mainKruizeExperimentMAP = new ConcurrentHashMap<>(); Map terms = new HashMap<>(); ValidationOutputData validationOutputData; @@ -311,6 +334,7 @@ public KruizeObject prepareRecommendations(int calCount) throws FetchMetricsErro /** * Generates recommendations for the specified KruizeObject + * * @param kruizeObject The KruizeObject containing experiment data */ public void generateRecommendations(KruizeObject kruizeObject) { @@ -322,7 +346,7 @@ public void generateRecommendations(KruizeObject kruizeObject) { NamespaceData namespaceData = k8sObject.getNamespaceData(); LOGGER.info("Generating recommendations for namespace: {}", namespaceName); generateRecommendationsBasedOnNamespace(namespaceData, kruizeObject); - } else if (kruizeObject.isContainerExperiment()){ + } else if (kruizeObject.isContainerExperiment()) { for (String containerName : k8sObject.getContainerDataMap().keySet()) { ContainerData containerData = k8sObject.getContainerDataMap().get(containerName); @@ -740,25 +764,6 @@ private MappedRecommendationForModel generateRecommendationBasedOnModel(Timestam return mappedRecommendationForModel; } - /** - * Calculates the number of pods for a namespace based on the provided results map. - * @param filteredResultsMap A map containing timestamp as keys and contains metric results for the corresponding timestamp. - * @return int maximum number of pods observed across all timestamps in the filtered results map. - */ - private static int getNumPodsForNamespace(Map filteredResultsMap) { - LOGGER.debug("Size of Filter Map: {}", filteredResultsMap.size()); - Double max_pods_cpu = filteredResultsMap.values() - .stream() - .map(e -> { - Optional numPodsResults = Optional.ofNullable(e.getMetricResultsMap().get(AnalyzerConstants.MetricName.namespaceTotalPods)); - double numPods = numPodsResults.map(m -> m.getAggregationInfoResult().getAvg()).orElse(0.0); - return numPods; - }) - .max(Double::compareTo).get(); - - return (int) Math.ceil(max_pods_cpu); - } - private void generateRecommendationsBasedOnNamespace(NamespaceData namespaceData, KruizeObject kruizeObject) { Timestamp monitoringEndTime = namespaceData.getResults().keySet().stream().max(Timestamp::compareTo).get(); NamespaceRecommendations namespaceRecommendations = namespaceData.getNamespaceRecommendations(); @@ -806,8 +811,8 @@ private void generateRecommendationsBasedOnNamespace(NamespaceData namespaceData } private HashMap> getCurrentNamespaceConfigData(NamespaceData namespaceData, - Timestamp monitoringEndTime, - MappedRecommendationForTimestamp timestampRecommendation) { + Timestamp monitoringEndTime, + MappedRecommendationForTimestamp timestampRecommendation) { HashMap> currentNamespaceConfig = new HashMap<>(); @@ -1096,13 +1101,13 @@ private MappedRecommendationForModel generateNamespaceRecommendationBasedOnModel * DO NOT EDIT THIS METHOD UNLESS THERE ARE ANY CHANGES TO BE ADDED IN VALIDATION OR POPULATION MECHANISM * EDITING THIS METHOD MIGHT LEAD TO UNEXPECTED OUTCOMES IN RECOMMENDATIONS, PLEASE PROCEED WITH CAUTION * - * @param termEntry The entry containing a term key and its associated {@link Terms} object. - * @param recommendationModel The model used to map recommendations. - * @param notifications A list to which recommendation notifications will be added. - * @param internalMapToPopulate The internal map to populate with recommendation configuration items. - * @param numPods The number of pods to consider for the recommendation. - * @param cpuThreshold The CPU usage threshold for the recommendation. - * @param memoryThreshold The memory usage threshold for the recommendation. + * @param termEntry The entry containing a term key and its associated {@link Terms} object. + * @param recommendationModel The model used to map recommendations. + * @param notifications A list to which recommendation notifications will be added. + * @param internalMapToPopulate The internal map to populate with recommendation configuration items. + * @param numPods The number of pods to consider for the recommendation. + * @param cpuThreshold The CPU usage threshold for the recommendation. + * @param memoryThreshold The memory usage threshold for the recommendation. * @param recommendationAcceleratorRequestMap The Map which has Accelerator recommendations * @return {@code true} if the internal map was successfully populated; {@code false} otherwise. */ @@ -1800,10 +1805,10 @@ private String getResults(Map mainKruizeExperimentMAP, Kru /** * Fetches metrics based on the specified datasource using queries from the metricProfile for the given time interval. * - * @param kruizeObject KruizeObject - * @param interval_end_time The end time of the interval in the format yyyy-MM-ddTHH:mm:sssZ - * @param interval_start_time The start time of the interval in the format yyyy-MM-ddTHH:mm:sssZ. - * @param dataSourceInfo DataSource object + * @param kruizeObject KruizeObject + * @param interval_end_time The end time of the interval in the format yyyy-MM-ddTHH:mm:sssZ + * @param interval_start_time The start time of the interval in the format yyyy-MM-ddTHH:mm:sssZ. + * @param dataSourceInfo DataSource object * @throws Exception */ public void fetchMetricsBasedOnProfileAndDatasource(KruizeObject kruizeObject, Timestamp interval_end_time, Timestamp interval_start_time, DataSourceInfo dataSourceInfo) throws Exception, FetchMetricsError { @@ -1839,12 +1844,13 @@ public void fetchMetricsBasedOnProfileAndDatasource(KruizeObject kruizeObject, T /** * Fetches namespace metrics based on the specified datasource using queries from the metricProfile for the given time interval. - * @param kruizeObject KruizeObject - * @param interval_end_time The end time of the interval in the format yyyy-MM-ddTHH:mm:sssZ - * @param interval_start_time The start time of the interval in the format yyyy-MM-ddTHH:mm:sssZ. - * @param dataSourceInfo DataSource object - * @param metricProfile performance profile to be used - * @param maxDateQuery max date query for namespace + * + * @param kruizeObject KruizeObject + * @param interval_end_time The end time of the interval in the format yyyy-MM-ddTHH:mm:sssZ + * @param interval_start_time The start time of the interval in the format yyyy-MM-ddTHH:mm:sssZ. + * @param dataSourceInfo DataSource object + * @param metricProfile performance profile to be used + * @param maxDateQuery max date query for namespace * @throws Exception */ private void fetchNamespaceMetricsBasedOnDataSourceAndProfile(KruizeObject kruizeObject, Timestamp interval_end_time, Timestamp interval_start_time, DataSourceInfo dataSourceInfo, PerformanceProfile metricProfile, String maxDateQuery) throws Exception, FetchMetricsError { @@ -1941,7 +1947,7 @@ private void fetchNamespaceMetricsBasedOnDataSourceAndProfile(KruizeObject kruiz LOGGER.info(promQL); String namespaceMetricsUrl; try { - namespaceMetricsUrl = String.format(KruizeConstants.DataSourceConstants.DATASOURCE_ENDPOINT_WITH_QUERY, + namespaceMetricsUrl = String.format(KruizeConstants.DataSourceConstants.DATASOURCE_ENDPOINT_WITH_QUERY_RANGE, dataSourceInfo.getUrl(), URLEncoder.encode(promQL, CHARACTER_ENCODING), interval_start_time_epoc, @@ -1995,12 +2001,12 @@ private void fetchNamespaceMetricsBasedOnDataSourceAndProfile(KruizeObject kruiz /** * Fetches Container metrics based on the specified datasource using queries from the metricProfile for the given time interval. * - * @param kruizeObject KruizeObject - * @param interval_end_time The end time of the interval in the format yyyy-MM-ddTHH:mm:sssZ - * @param interval_start_time The start time of the interval in the format yyyy-MM-ddTHH:mm:sssZ. - * @param dataSourceInfo DataSource object - * @param metricProfile performance profile to be used - * @param maxDateQuery max date query for containers + * @param kruizeObject KruizeObject + * @param interval_end_time The end time of the interval in the format yyyy-MM-ddTHH:mm:sssZ + * @param interval_start_time The start time of the interval in the format yyyy-MM-ddTHH:mm:sssZ. + * @param dataSourceInfo DataSource object + * @param metricProfile performance profile to be used + * @param maxDateQuery max date query for containers * @throws Exception */ private void fetchContainerMetricsBasedOnDataSourceAndProfile(KruizeObject kruizeObject, @@ -2052,7 +2058,7 @@ private void fetchContainerMetricsBasedOnDataSourceAndProfile(KruizeObject kruiz LOGGER.debug("maxDateQuery: {}", maxDateQuery); - queryToEncode = maxDateQuery + queryToEncode = maxDateQuery .replace(AnalyzerConstants.NAMESPACE_VARIABLE, namespace) .replace(AnalyzerConstants.CONTAINER_VARIABLE, containerName) .replace(AnalyzerConstants.WORKLOAD_VARIABLE, workload) @@ -2125,7 +2131,7 @@ private void fetchContainerMetricsBasedOnDataSourceAndProfile(KruizeObject kruiz continue; HashMap aggregationFunctions = metricEntry.getAggregationFunctionsMap(); - for (Map.Entry aggregationFunctionsEntry: aggregationFunctions.entrySet()) { + for (Map.Entry aggregationFunctionsEntry : aggregationFunctions.entrySet()) { // Determine promQL query on metric type String promQL = aggregationFunctionsEntry.getValue().getQuery(); @@ -2157,7 +2163,7 @@ private void fetchContainerMetricsBasedOnDataSourceAndProfile(KruizeObject kruiz LOGGER.debug(promQL); String podMetricsUrl; try { - podMetricsUrl = String.format(KruizeConstants.DataSourceConstants.DATASOURCE_ENDPOINT_WITH_QUERY, + podMetricsUrl = String.format(KruizeConstants.DataSourceConstants.DATASOURCE_ENDPOINT_WITH_QUERY_RANGE, dataSourceInfo.getUrl(), URLEncoder.encode(promQL, CHARACTER_ENCODING), interval_start_time_epoc, @@ -2174,7 +2180,7 @@ private void fetchContainerMetricsBasedOnDataSourceAndProfile(KruizeObject kruiz continue; // Process fetched metrics - if (isAcceleratorMetric){ + if (isAcceleratorMetric) { for (JsonElement result : resultArray) { JsonObject resultObject = result.getAsJsonObject(); JsonObject metricObject = resultObject.getAsJsonObject(KruizeConstants.JSONKeys.METRIC); @@ -2316,13 +2322,14 @@ private void fetchContainerMetricsBasedOnDataSourceAndProfile(KruizeObject kruiz /** * Fetches max date query for namespace and containers from performance profile - * @param metricProfile performance profile to be used + * + * @param metricProfile performance profile to be used */ private String getMaxDateQuery(PerformanceProfile metricProfile, String metricName) { List metrics = metricProfile.getSloInfo().getFunctionVariables(); - for (Metric metric: metrics) { + for (Metric metric : metrics) { String name = metric.getName(); - if(name.equals(metricName)) { + if (name.equals(metricName)) { return metric.getAggregationFunctionsMap().get("max").getQuery(); } } @@ -2376,6 +2383,7 @@ private void prepareIntervalResults(Map dataResultsM /** * Filters out maxDateQuery and includes metrics based on the experiment type and kubernetes_object + * * @param metricProfile Metric profile to be used * @param maxDateQuery maxDateQuery metric to be filtered out * @param experimentType experiment type @@ -2384,17 +2392,17 @@ public List filterMetricsBasedOnExpTypeAndK8sObject(PerformanceProfile m String namespace = KruizeConstants.JSONKeys.NAMESPACE; String container = KruizeConstants.JSONKeys.CONTAINER; return metricProfile.getSloInfo().getFunctionVariables().stream() - .filter(Metric -> { - String name = Metric.getName(); - String kubernetes_object = Metric.getKubernetesObject(); - - // Include metrics based on experiment_type, kubernetes_object and exclude maxDate metric - return !name.equals(maxDateQuery) && ( - (experimentType.equals(AnalyzerConstants.ExperimentTypes.NAMESPACE_EXPERIMENT) && kubernetes_object.equals(namespace)) || - (experimentType.equals(AnalyzerConstants.ExperimentTypes.CONTAINER_EXPERIMENT) && kubernetes_object.equals(container)) - ); - }) - .toList(); + .filter(Metric -> { + String name = Metric.getName(); + String kubernetes_object = Metric.getKubernetesObject(); + + // Include metrics based on experiment_type, kubernetes_object and exclude maxDate metric + return !name.equals(maxDateQuery) && ( + (experimentType.equals(AnalyzerConstants.ExperimentTypes.NAMESPACE_EXPERIMENT) && kubernetes_object.equals(namespace)) || + (experimentType.equals(AnalyzerConstants.ExperimentTypes.CONTAINER_EXPERIMENT) && kubernetes_object.equals(container)) + ); + }) + .toList(); } } diff --git a/src/main/java/com/autotune/analyzer/recommendations/utils/RecommendationUtils.java b/src/main/java/com/autotune/analyzer/recommendations/utils/RecommendationUtils.java index 45085f33c..ce65ce5aa 100644 --- a/src/main/java/com/autotune/analyzer/recommendations/utils/RecommendationUtils.java +++ b/src/main/java/com/autotune/analyzer/recommendations/utils/RecommendationUtils.java @@ -10,6 +10,8 @@ import com.autotune.common.data.result.IntervalResults; import com.autotune.common.data.system.info.device.ContainerDeviceList; import com.autotune.common.data.system.info.device.accelerator.AcceleratorDeviceData; +import com.autotune.common.data.system.info.device.accelerator.metadata.AcceleratorMetaDataService; +import com.autotune.common.data.system.info.device.accelerator.metadata.AcceleratorProfile; import com.autotune.common.datasource.DataSourceInfo; import com.autotune.utils.GenericRestApiClient; import com.autotune.utils.KruizeConstants; @@ -17,8 +19,6 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.autotune.common.data.system.info.device.accelerator.metadata.AcceleratorMetaDataService; -import com.autotune.common.data.system.info.device.accelerator.metadata.AcceleratorProfile; import java.io.IOException; import java.net.URLEncoder; @@ -34,6 +34,7 @@ public class RecommendationUtils { private static final Logger LOGGER = LoggerFactory.getLogger(RecommendationUtils.class); + public static RecommendationConfigItem getCurrentValue(Map filteredResultsMap, Timestamp timestampToExtract, AnalyzerConstants.ResourceSetting resourceSetting, @@ -153,15 +154,15 @@ else if (resourceSetting == AnalyzerConstants.ResourceSetting.limits) { } } - public static void markAcceleratorDeviceStatusToContainer (ContainerData containerData, - String maxDateQuery, - String namespace, - String workload, - String workload_type, - DataSourceInfo dataSourceInfo, - Map termsMap, - Double measurementDurationMinutesInDouble, - String gpuDetectionQuery) + public static void markAcceleratorDeviceStatusToContainer(ContainerData containerData, + String maxDateQuery, + String namespace, + String workload, + String workload_type, + DataSourceInfo dataSourceInfo, + Map termsMap, + Double measurementDurationMinutesInDouble, + String gpuDetectionQuery) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, ParseException, FetchMetricsError { @@ -172,7 +173,7 @@ public static void markAcceleratorDeviceStatusToContainer (ContainerData contain long interval_start_time_epoc = 0; LOGGER.debug("maxDateQuery: {}", maxDateQuery); - queryToEncode = maxDateQuery + queryToEncode = maxDateQuery .replace(AnalyzerConstants.NAMESPACE_VARIABLE, namespace) .replace(AnalyzerConstants.CONTAINER_VARIABLE, containerName) .replace(AnalyzerConstants.WORKLOAD_VARIABLE, workload) @@ -218,7 +219,7 @@ public static void markAcceleratorDeviceStatusToContainer (ContainerData contain String podMetricsUrl; try { - podMetricsUrl = String.format(KruizeConstants.DataSourceConstants.DATASOURCE_ENDPOINT_WITH_QUERY, + podMetricsUrl = String.format(KruizeConstants.DataSourceConstants.DATASOURCE_ENDPOINT_WITH_QUERY_RANGE, dataSourceInfo.getUrl(), URLEncoder.encode(gpuDetectionQuery, CHARACTER_ENCODING), interval_start_time_epoc, diff --git a/src/main/java/com/autotune/common/datasource/DataSourceMetadataOperator.java b/src/main/java/com/autotune/common/datasource/DataSourceMetadataOperator.java index c66cde76c..ff50be82d 100644 --- a/src/main/java/com/autotune/common/datasource/DataSourceMetadataOperator.java +++ b/src/main/java/com/autotune/common/datasource/DataSourceMetadataOperator.java @@ -15,7 +15,6 @@ *******************************************************************************/ package com.autotune.common.datasource; -import com.autotune.analyzer.exceptions.FetchMetricsError; import com.autotune.common.data.dataSourceMetadata.*; import com.autotune.common.data.dataSourceQueries.PromQLDataSourceQueries; import com.autotune.utils.GenericRestApiClient; @@ -27,10 +26,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; import java.io.IOException; import java.net.URLEncoder; import java.security.KeyManagementException; @@ -71,7 +66,7 @@ public static DataSourceMetadataOperator getInstance() { * @param startTime Get metadata from starttime to endtime * @param endTime Get metadata from starttime to endtime * @param steps the interval between data points in a range query - * TODO - support multiple data sources + * TODO - support multiple data sources */ public DataSourceMetadataInfo createDataSourceMetadata(DataSourceInfo dataSourceInfo, String uniqueKey, long startTime, long endTime, int steps) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { return processQueriesAndPopulateDataSourceMetadataInfo(dataSourceInfo, uniqueKey, startTime, endTime, steps); @@ -113,8 +108,8 @@ public DataSourceMetadataInfo getDataSourceMetadataInfo(DataSourceInfo dataSourc * @param dataSourceInfo The DataSourceInfo object containing information about the * data source to be updated. *

- * TODO - Currently Create and Update functions have identical functionalities, based on UI workflow and requirements - * need to further enhance updateDataSourceMetadata() to support namespace, workload level granular updates + * TODO - Currently Create and Update functions have identical functionalities, based on UI workflow and requirements + * need to further enhance updateDataSourceMetadata() to support namespace, workload level granular updates */ public DataSourceMetadataInfo updateDataSourceMetadata(DataSourceInfo dataSourceInfo, String uniqueKey, long startTime, long endTime, int steps) throws Exception { return processQueriesAndPopulateDataSourceMetadataInfo(dataSourceInfo, uniqueKey, startTime, endTime, steps); @@ -251,12 +246,21 @@ public DataSourceMetadataInfo processQueriesAndPopulateDataSourceMetadataInfo(Da private JsonArray fetchQueryResults(DataSourceInfo dataSourceInfo, String query, long startTime, long endTime, int steps) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { GenericRestApiClient client = new GenericRestApiClient(dataSourceInfo); - String metricsUrl = String.format(KruizeConstants.DataSourceConstants.DATASOURCE_ENDPOINT_WITH_QUERY, - dataSourceInfo.getUrl(), - URLEncoder.encode(query, CHARACTER_ENCODING), - startTime, - endTime, - steps); + String metricsUrl; + if (startTime != 0 && endTime != 0 && steps != 0) { + metricsUrl = String.format(KruizeConstants.DataSourceConstants.DATASOURCE_ENDPOINT_WITH_QUERY_RANGE, + dataSourceInfo.getUrl(), + URLEncoder.encode(query, CHARACTER_ENCODING), + startTime, + endTime, + steps); + } else { + metricsUrl = String.format(KruizeConstants.DataSourceConstants.DATE_ENDPOINT_WITH_QUERY, + dataSourceInfo.getUrl(), + URLEncoder.encode(query, CHARACTER_ENCODING) + ); + } + LOGGER.debug("MetricsUrl: {}", metricsUrl); client.setBaseURL(metricsUrl); JSONObject genericJsonObject = client.fetchMetricsJson(KruizeConstants.APIMessages.GET, ""); diff --git a/src/main/java/com/autotune/utils/KruizeConstants.java b/src/main/java/com/autotune/utils/KruizeConstants.java index f863b38d4..11b22df33 100644 --- a/src/main/java/com/autotune/utils/KruizeConstants.java +++ b/src/main/java/com/autotune/utils/KruizeConstants.java @@ -407,7 +407,7 @@ public static class DataSourceConstants { public static final String PROMETHEUS_DEFAULT_SERVICE_PORT = "9090"; public static final String OPENSHIFT_MONITORING_PROMETHEUS_DEFAULT_SERVICE_PORT = "9091"; public static final String PROMETHEUS_REACHABILITY_QUERY = "up"; - public static final String DATASOURCE_ENDPOINT_WITH_QUERY = "%s/api/v1/query_range?query=%s&start=%s&end=%s&step=%s"; + public static final String DATASOURCE_ENDPOINT_WITH_QUERY_RANGE = "%s/api/v1/query_range?query=%s&start=%s&end=%s&step=%s"; public static final String DATE_ENDPOINT_WITH_QUERY = "%s/api/v1/query?query=%s"; private DataSourceConstants() {