Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,36 @@ More details around metric prefix can be found [here](https://community.appdynam
regions: ["eu-central-1","eu-west-1"]
~~~

3. Provide the list of Api names that needs to be monitored. This list accepts regular expressions.
3. Provide the list of Api Ids that needs to be monitored. This list accepts regular expressions. (Previously api names were expected but the field has been deprecated)

~~~
apiNames: ["api1", "api2"]
apiId: ["api1", "api2"]
~~~

4. Configure the `metricsConfig` section to control how metrics are collected from CloudWatch:

**metricsTimeRange**: Controls the time window for metric collection
- `startTimeInMinsBeforeNow`: How many minutes back from current time to start collecting metrics (default: 60)
- `endTimeInMinsBeforeNow`: How many minutes back from current time to end collecting metrics (default: 0, meaning current time)

**getMetricStatisticsRateLimit**: Rate limit per second for CloudWatch GetMetricStatistics API calls (default: 400).
This helps avoid hitting AWS API throttling limits. See [AWS CloudWatch limits](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_limits.html) for more details.

**maxErrorRetrySize**: Maximum number of retry attempts for failed requests or throttling errors (default: 0, meaning no retries).

**defaultPeriod**: Default time period in seconds for all metrics. Must be a multiple of 60 (default: 300).
Valid values include 60, 300, 3600, etc. Individual metrics can override this using the 'period' field in their specific configuration.

~~~
metricsConfig:
metricsTimeRange:
startTimeInMinsBeforeNow: 60
endTimeInMinsBeforeNow: 0
getMetricStatisticsRateLimit: 400
maxErrorRetrySize: 0
defaultPeriod: 300
~~~

## Metrics

1. 4XXError - The number of client-side errors captured in a specified period.
Expand Down
44 changes: 32 additions & 12 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
<dependency>
<groupId>com.appdynamics.extensions</groupId>
<artifactId>aws-cloudwatch-exts-commons</artifactId>
<version>2.2.5</version>
<version>2.2.8</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-api-gateway</artifactId>
<version>1.11.642</version>
<version>1.12.788</version>
</dependency>
<dependency>
<groupId>junit</groupId>
Expand All @@ -40,15 +40,15 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.3</version>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.3</version>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand All @@ -59,10 +59,30 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<version>3.14.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<argLine>
--add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.lang.invoke=ALL-UNNAMED
--add-opens java.base/java.util.regex=ALL-UNNAMED
--add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.xml=ALL-UNNAMED
--add-exports java.xml/jdk.xml.internal=ALL-UNNAMED
--add-opens java.base/java.util.stream=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
<plugin>
Expand Down Expand Up @@ -133,7 +153,7 @@
<fileset dir="${project.basedir}" includes="LICENSE.txt" />
</copy>
<copy todir="${target.dir}">
<fileset dir="${build.directory}" includes="${project.artifactId}.${project.packaging}" />
<fileset dir="${project.build.directory}" includes="${project.artifactId}.${project.packaging}" />
</copy>
<zip destfile="${target.dir}-${project.version}.zip">
<zipfileset dir="${target.dir}" filemode="755" prefix="${target.name}/" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package com.appdynamics.extensions.aws.apigateway;


import com.amazonaws.services.cloudwatch.model.Metric;
import software.amazon.awssdk.services.cloudwatch.model.Metric;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
Expand All @@ -28,18 +28,18 @@
*/
public class ApiNamesPredicate implements Predicate<Metric> {

private List<String> apiNamesList;
private List<String> apiIdList;
private Predicate<CharSequence> patternPredicate;

public ApiNamesPredicate(List<String> apiNamesList){
this.apiNamesList = apiNamesList;
public ApiNamesPredicate(List<String> apiIdList){
this.apiIdList = apiIdList;
buildPattern();
}

private void buildPattern(){
if(apiNamesList != null && !apiNamesList.isEmpty()){
if(apiIdList != null && !apiIdList.isEmpty()){

for(String apiPattern : apiNamesList){
for(String apiPattern : apiIdList){
if(!Strings.isNullOrEmpty(apiPattern)) {
Predicate<CharSequence> apiPatternPredicate = Predicates.containsPattern(apiPattern);
if (patternPredicate == null) {
Expand All @@ -58,7 +58,7 @@ public boolean apply(Metric metric) {
return true;
}
else{
String apiName = metric.getDimensions().get(0).getValue();
String apiName = metric.dimensions().get(0).value();
return patternPredicate.apply(apiName);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
public class APIGatewayConfiguration extends Configuration {

private Map<String, ?> eventsService;
private List<String> apiNames;
private List<String> apiId;


public void setEventsService(Map<String, ?> eventsService) {
Expand All @@ -38,11 +38,11 @@ public void setEventsService(Map<String, ?> eventsService) {
return eventsService;
}

public void setApiNames(List<String> apiNames) {
this.apiNames = apiNames;
public void setApiId(List<String> apiId) {
this.apiId = apiId;
}

public List<String> getApiNames() {
return apiNames;
public List<String> getApiId() {
return apiId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@

package com.appdynamics.extensions.aws.apigateway.processors;

import com.amazonaws.services.cloudwatch.AmazonCloudWatch;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.DimensionFilter;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
import software.amazon.awssdk.services.cloudwatch.model.DimensionFilter;
import com.appdynamics.extensions.aws.apigateway.ApiNamesPredicate;
import com.appdynamics.extensions.aws.apigateway.EventsServiceMetricsWriter;
import com.appdynamics.extensions.aws.apigateway.configuration.APIGatewayConfiguration;
import com.appdynamics.extensions.aws.apigateway.configuration.EventsService;

import com.appdynamics.extensions.aws.config.IncludeMetric;
import com.appdynamics.extensions.aws.dto.AWSMetric;
import com.appdynamics.extensions.aws.metric.*;
Expand All @@ -31,6 +31,9 @@
import com.appdynamics.extensions.metrics.Metric;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;



import org.slf4j.Logger;


Expand All @@ -47,20 +50,21 @@ public class APIGatewayMetricsProcessor implements MetricsProcessor {
private static final Logger logger = ExtensionsLoggerFactory.getLogger(APIGatewayMetricsProcessor.class);
private static final String NAMESPACE = "AWS/ApiGateway";
private static final String APINAME = "ApiName";
private static final String APIID = "ApiId";
private List<IncludeMetric> includeMetrics;
private List<String> apiNamesList;
private List<String> apiIdList;
private APIGatewayConfiguration apiGatewayConfiguration;
private Map<String, ?> eventsService;

public APIGatewayMetricsProcessor(APIGatewayConfiguration apiGatewayConfiguration){
this.apiGatewayConfiguration = apiGatewayConfiguration;
this.includeMetrics = apiGatewayConfiguration.getMetricsConfig().getIncludeMetrics();
this.apiNamesList = apiGatewayConfiguration.getApiNames();
this.apiIdList = apiGatewayConfiguration.getApiId();
this.eventsService = apiGatewayConfiguration.getEventsService();
}

@Override
public List<AWSMetric> getMetrics(AmazonCloudWatch awsCloudWatch, String accountName, LongAdder awsRequestsCounter) {
public List<AWSMetric> getMetrics(CloudWatchClient cloudWatchClient, String accountName, LongAdder awsRequestsCounter) {
/*The dimension being used here for filtering is "ApiName".
* Another available dimension is "ApiName" and "Stage".
* The "ApiName" dimension filter will retrieve metrics with just the
Expand All @@ -75,20 +79,30 @@ public List<AWSMetric> getMetrics(AmazonCloudWatch awsCloudWatch, String account
* Since the dimension used for filtering is "ApiName", we can filter
* further with the ApiName values.
* */
ApiNamesPredicate apiNamesPredicate = new ApiNamesPredicate(apiNamesList);
ApiNamesPredicate apiNamesPredicate = new ApiNamesPredicate(apiIdList);

return MetricsProcessorHelper.getFilteredMetrics(awsCloudWatch, awsRequestsCounter, NAMESPACE, includeMetrics, dimensionFilters, apiNamesPredicate);
return MetricsProcessorHelper.getFilteredMetrics(cloudWatchClient, awsRequestsCounter, NAMESPACE, includeMetrics, dimensionFilters, apiNamesPredicate);
}

private List<DimensionFilter> getDimensionFilters(){
List<DimensionFilter> dimensionFilters = Lists.newArrayList();

DimensionFilter apiNameDimensionFilter = new DimensionFilter();
apiNameDimensionFilter.withName(APINAME);
DimensionFilter apiNameDimensionFilter = DimensionFilter.builder()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of APIName, why 3 diff dimention filters were created?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ApiName is not a filter in cloudwatch. they changed the schema to ApiId,Stage and Resource
image

.name(APIID)
.build();
DimensionFilter resourceNameDimensionFilter = DimensionFilter.builder()
.name("Resource")
.build();
DimensionFilter stageNameDimensionFilter = DimensionFilter.builder()
.name("Stage")
.build();

dimensionFilters.add(apiNameDimensionFilter);
dimensionFilters.add(stageNameDimensionFilter);
dimensionFilters.add(resourceNameDimensionFilter);

/*DimensionFilter stageDimensionFilter = new DimensionFilter();
stageDimensionFilter.withName("Stage");
stageDimensionFilter.setName("Stage");
dimensionFilters.add(stageDimensionFilter);*/

return dimensionFilters;
Expand Down Expand Up @@ -148,16 +162,20 @@ private void uploadToEventsServiceIfEnabled(List<Metric> metricList){
private String createMetricPath(String accountName, String region, MetricStatistic metricStatistic){
AWSMetric awsMetric = metricStatistic.getMetric();
IncludeMetric includeMetric = awsMetric.getIncludeMetric();
com.amazonaws.services.cloudwatch.model.Metric metric = awsMetric.getMetric();
software.amazon.awssdk.services.cloudwatch.model.Metric metric = awsMetric.getMetric();
String apiName = null;
String stageName = null;
String routeName = null;

for(Dimension dimension : metric.getDimensions()) {
if(dimension.getName().equalsIgnoreCase("ApiName")) {
apiName = dimension.getValue();
for(Dimension dimension : metric.dimensions()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

if(dimension.name().equalsIgnoreCase("ApiId")) {
apiName = dimension.value();
}
if(dimension.name().equalsIgnoreCase("Stage")) {
stageName = dimension.value();
}
if(dimension.getName().equalsIgnoreCase("Stage")) {
stageName = dimension.getValue();
if(dimension.name().equalsIgnoreCase("Resource")) {
routeName = dimension.value();
}
}
//apiName will never be null
Expand All @@ -171,7 +189,12 @@ private String createMetricPath(String accountName, String region, MetricStatist
stringBuilder.append(stageName)
.append("|");
}
if(routeName != null) {
stringBuilder.append(routeName)
.append("|");
}
stringBuilder.append(includeMetric.getName());
logger.info(stringBuilder.toString());
return stringBuilder.toString();

}
Expand Down
10 changes: 6 additions & 4 deletions src/main/resources/conf/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ accounts:
displayAccountName: "AppD"
regions: ["us-west-2"]

apiNames: []
apiId: []

metricsConfig:
# Global time range configuration, applicable to all the metrics configured below.
Expand All @@ -36,6 +36,10 @@ metricsConfig:
# The max number of retry attempts for failed retryable requests
# (ex: 5xx error responses from a service) or throttling errors
maxErrorRetrySize: 0
# Default period for all metrics (in seconds). Must be a multiple of 60.
# Valid values: 60, 300, 3600, etc.
# Individual metrics can override this value using the 'period' field in the specific includeMetrics section
defaultPeriod: 300
# By default, all metrics retrieved from cloudwatch are 'Average' values.
# This option allows you to override the metric type.
# Allowed statTypes are: ave, max, min, sum, samplecount
Expand All @@ -53,9 +57,6 @@ metricsConfig:
multiplier: 1
timeRollUpType: "AVERAGE"
clusterRollUpType: "INDIVIDUAL"
metricsTimeRange:
startTimeInMinsBeforeNow: 10
endTimeInMinsBeforeNow: 0
- name: "5XXError"
alias: "5XXError"
statType: "ave"
Expand Down Expand Up @@ -103,6 +104,7 @@ regionEndPoints:
ap-northeast-1: monitoring.ap-northeast-1.amazonaws.com
ap-southeast-1: monitoring.ap-southeast-1.amazonaws.com
ap-southeast-2: monitoring.ap-southeast-2.amazonaws.com
ap-south-1: monitoring.ap-south-1.amazonaws.com
eu-central-1: monitoring.eu-central-1.amazonaws.com
us-east-1: monitoring.us-east-1.amazonaws.com
us-west-1: monitoring.us-west-1.amazonaws.com
Expand Down
Loading