![JAR search and dependency download from the Maven repository](/logo.png)
com.sap.cloud.sdk.cloudplatform.metering.MeteringStats Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metering-scp-neo Show documentation
Show all versions of metering-scp-neo Show documentation
Implementation of the Cloud platform abstraction for API metering functionality
on the SAP Cloud Platform (Neo).
/*
* Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.cloud.sdk.cloudplatform.metering;
import java.io.IOException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Map;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.slf4j.Logger;
import com.google.common.util.concurrent.AtomicLongMap;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.sap.cloud.account.TenantContext;
import com.sap.cloud.sdk.cloudplatform.CloudPlatform;
import com.sap.cloud.sdk.cloudplatform.CloudPlatformAccessor;
import com.sap.cloud.sdk.cloudplatform.ScpNeoCloudPlatform;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpEntityUtil;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException;
import com.sap.cloud.sdk.cloudplatform.logging.CloudLoggerFactory;
import com.sap.cloud.sdk.cloudplatform.security.Role;
import com.sap.cloud.sdk.cloudplatform.tenant.ScpNeoTenant;
import com.sap.cloud.sdk.cloudplatform.tenant.TenantAccessor;
import com.sap.cloud.sdk.cloudplatform.tenant.exception.TenantAccessException;
import com.sap.cloud.sdk.cloudplatform.tenant.exception.TenantNotAvailableException;
/**
* Stores access numbers to the different APIs.
*/
public class MeteringStats
{
private static final Logger logger = CloudLoggerFactory.getSanitizedLogger(MeteringStats.class);
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'").withZone(ZoneId.of("UTC")).withLocale(Locale.US);
private final AtomicLongMap apiCounter = AtomicLongMap.create();
private final String serviceId;
private final Role meteringRole;
private final String meteringServiceDestinationName;
/**
* Initializes a new {@code MeteringStats} with the given service id, role and metering service destination name.
*
* @param serviceId
* The id needed for the metering service.
* @param meteringRole
* The role needed to obtain tenant information.
* @param meteringServiceDestinationName
* The name of the metering destination.
*/
public MeteringStats( final String serviceId, final Role meteringRole, final String meteringServiceDestinationName )
{
this.serviceId = serviceId;
this.meteringRole = meteringRole;
this.meteringServiceDestinationName = meteringServiceDestinationName;
}
/**
* Record a single access by the tenant to the API specified by the key.
*
* @param key
* The metric key.
* @return The updated number of accesses.
*/
public long record( final TenantMetricKeyPair key )
{
return apiCounter.addAndGet(key, 1);
}
/**
* Record a specified number accesses by a the tenant to the API specified by the key.
*
* @param key
* The metric key.
* @param increment
* The number of API accesses to record.
* @return The updated number of accesses.
*/
public long record( final TenantMetricKeyPair key, final long increment )
{
return apiCounter.addAndGet(key, increment);
}
/**
* Getter for the current number of API accesses.
*
* @param key
* The metric key to get the number of accesses for.
* @return The number of accesses of the tenant to the API specified by the key.
*/
public long getApiCountsFor( final TenantMetricKeyPair key )
{
return apiCounter.get(key);
}
/**
* Aggregates the number of API accesses over all entries.
*
* @return The number of overall API accesses.
*/
public long getSumOfApiCounts()
{
return apiCounter.sum();
}
/**
* Number of metric key stored.
*
* @return The number of stored metric keys.
*/
public long getDistinctApiCounts()
{
return apiCounter.size();
}
private TenantContext getCurrentTenantContext()
throws TenantNotAvailableException,
TenantAccessException
{
return ((ScpNeoTenant) TenantAccessor.getCurrentTenant()).getTenantContext();
}
private TenantUserPair getNumberOfTenantUsers( final String tenantId )
{
try {
return getCurrentTenantContext().execute(tenantId, new AuthorizationServiceCallable(meteringRole));
}
catch( final Exception e ) {
logger.error("Failed to execute call to authorization API on behalf of tenant " + tenantId + ".", e);
}
return null;
}
/**
* Sends the stored data to the metering service.
*
* @throws TenantNotAvailableException
* If there is currently no Tenant available. This typically occurs when trying to access the tenant
* within code that is running outside any tenant context, e.g., within a background task.
*
* @throws TenantAccessException
* If there is an issue while accessing the Tenant.
*/
public void sendToMeteringService()
throws TenantNotAvailableException,
TenantAccessException
{
if( logger.isInfoEnabled() ) {
logger.info("Sending collected results to metering service.");
}
final HttpClient httpClient;
try {
httpClient = HttpClientAccessor.getHttpClient(meteringServiceDestinationName);
}
catch( final DestinationNotFoundException | DestinationAccessException e ) {
logger.error("Failed to send metering results to metering service.", e);
return;
}
final String sampleTimestamp = ZonedDateTime.now().format(DATE_TIME_FORMATTER);
final JsonArray payload = new JsonArray();
final CloudPlatform cloudPlatform = CloudPlatformAccessor.getCloudPlatform();
if( !(cloudPlatform instanceof ScpNeoCloudPlatform) ) {
throw new ShouldNotHappenException(
"The current Cloud platform is not an instance of "
+ ScpNeoCloudPlatform.class.getSimpleName()
+ ". Please make sure to specify a dependency to com.sap.cloud.s4hana.cloudplatform:core-scp-neo.");
}
final ScpNeoCloudPlatform scpNeoCloudPlatform = (ScpNeoCloudPlatform) cloudPlatform;
for( final com.sap.cloud.account.Tenant tenant : getCurrentTenantContext().getSubscribedTenants() ) {
final String tenantId = tenant.getId();
if( tenantId.equals(scpNeoCloudPlatform.getProviderAccountId()) ) {
continue;
}
if( logger.isDebugEnabled() ) {
logger.debug(
"Processing tenantId: "
+ tenantId
+ ", accountId: "
+ tenant.getAccount().getId()
+ ", accountName: "
+ tenant.getAccount().getName()
+ ".");
}
final TenantUserPair tenantUserPair = getNumberOfTenantUsers(tenantId);
if( tenantUserPair != null && tenantUserPair.getNumberOfUsers() > 0 ) {
if( logger.isDebugEnabled() ) {
logger.debug("Processing tenant and user pair: " + tenantUserPair + ".");
}
final JsonObject metric = new JsonObject();
metric.addProperty("metricKey", serviceId + ".AssignedUsers");
metric.addProperty("account", scpNeoCloudPlatform.getProviderAccountId());
metric.addProperty("application", cloudPlatform.getApplicationName());
metric.addProperty("tenantUuid", tenantUserPair.getTenantId());
metric.addProperty("tenantAccount", tenantUserPair.getAccountId());
metric.addProperty("instant", sampleTimestamp);
metric.addProperty("value", tenantUserPair.getNumberOfUsers());
payload.add(metric);
}
}
final Map bufferMap = apiCounter.asMap();
for( final Map.Entry entry : bufferMap.entrySet() ) {
if( entry.getValue() > 0 ) {
final JsonObject metric = new JsonObject();
metric.addProperty("metricKey", serviceId + "." + entry.getKey().getMeteringCategory());
metric.addProperty("account", scpNeoCloudPlatform.getProviderAccountId());
metric.addProperty("application", cloudPlatform.getApplicationName());
metric.addProperty("tenantUuid", entry.getKey().getTenantId());
metric.addProperty("tenantAccount", entry.getKey().getAccountId());
metric.addProperty("instant", sampleTimestamp);
metric.addProperty("value", entry.getValue());
payload.add(metric);
}
}
// FIXME wrap remote call in Hystrix command and handle Hystrix exceptions (HystrixRuntimeException etc.)
try {
final HttpPost post = new HttpPost("/");
post.setEntity(new StringEntity(payload.toString()));
post.setHeader("Content-Type", "application/json");
final HttpResponse response = httpClient.execute(post);
final int statusCode = response.getStatusLine().getStatusCode();
if( statusCode == HttpStatus.SC_NO_CONTENT ) {
for( final Map.Entry entry : bufferMap.entrySet() ) {
apiCounter.put(entry.getKey(), apiCounter.get(entry.getKey()) - entry.getValue());
}
apiCounter.removeAllZeros();
} else {
logger.error(
"Metering service responded with error "
+ statusCode
+ ". Payload: "
+ HttpEntityUtil.getResponseBody(response)
+ ".");
}
}
catch( final IOException e ) {
logger.error("Failed to send results to metering service.", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy