/*
* Copyright 2015-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.glowroot.central.repo;
import java.util.List;
import java.util.Map;
import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.MoreExecutors;
import org.immutables.value.Value;
import org.glowroot.central.util.Cache;
import org.glowroot.central.util.Cache.CacheLoader;
import org.glowroot.central.util.ClusterManager;
import org.glowroot.central.util.RateLimiter;
import org.glowroot.central.util.Sessions;
import org.glowroot.common.repo.ConfigRepository;
import org.glowroot.common.repo.TransactionTypeRepository;
import org.glowroot.common.util.Styles;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.HOURS;
public class TransactionTypeDao implements TransactionTypeRepository {
private static final String WITH_LCS =
"with compaction = { 'class' : 'LeveledCompactionStrategy' }";
private static final String SINGLE_CACHE_KEY = "x";
private final Session session;
private final ConfigRepository configRepository;
private final PreparedStatement insertPS;
private final PreparedStatement readPS;
private final RateLimiter<TransactionTypeKey> rateLimiter = new RateLimiter<>();
private final Cache<String, Map<String, List<String>>> transactionTypesCache;
public TransactionTypeDao(Session session, ConfigRepository configRepository,
ClusterManager clusterManager) {
this.session = session;
this.configRepository = configRepository;
session.execute("create table if not exists transaction_type (one int,"
+ " agent_rollup varchar, transaction_type varchar, primary key"
+ " (one, agent_rollup, transaction_type)) " + WITH_LCS);
insertPS = session.prepare("insert into transaction_type (one, agent_rollup,"
+ " transaction_type) values (1, ?, ?) using ttl ?");
readPS = session.prepare(
"select agent_rollup, transaction_type from transaction_type where one = 1");
transactionTypesCache = clusterManager.createCache("transactionTypesCache",
new TransactionTypeCacheLoader());
}
@Override
public Map<String, List<String>> read() throws Exception {
return transactionTypesCache.get(SINGLE_CACHE_KEY);
}
List<ResultSetFuture> store(List<String> agentRollups, String transactionType)
throws Exception {
List<ResultSetFuture> futures = Lists.newArrayList();
for (String agentRollupId : agentRollups) {
TransactionTypeKey rateLimiterKey =
ImmutableTransactionTypeKey.of(agentRollupId, transactionType);
if (!rateLimiter.tryAcquire(rateLimiterKey)) {
continue;
}
BoundStatement boundStatement = insertPS.bind();
int i = 0;
boundStatement.setString(i++, agentRollupId);
boundStatement.setString(i++, transactionType);
boundStatement.setInt(i++, getMaxTTL());
ResultSetFuture future = Sessions.executeAsyncWithOnFailure(session, boundStatement,
() -> rateLimiter.invalidate(rateLimiterKey));
future.addListener(() -> transactionTypesCache.invalidate(SINGLE_CACHE_KEY),
MoreExecutors.directExecutor());
futures.add(future);
}
return futures;
}
private int getMaxTTL() throws Exception {
long maxTTL = 0;
for (long expirationHours : configRepository.getStorageConfig().rollupExpirationHours()) {
if (expirationHours == 0) {
// zero value expiration/TTL means never expire
return 0;
}
maxTTL = Math.max(maxTTL, HOURS.toSeconds(expirationHours));
}
// intentionally not accounting for rateLimiter
return Ints.saturatedCast(maxTTL);
}
@Value.Immutable
@Styles.AllParameters
interface TransactionTypeKey {
String agentRollupId();
String transactionType();
}
private class TransactionTypeCacheLoader
implements CacheLoader<String, Map<String, List<String>>> {
@Override
public Map<String, List<String>> load(String key) {
ResultSet results = session.execute(readPS.bind());
ImmutableMap.Builder<String, List<String>> builder = ImmutableMap.builder();
String currAgentRollup = null;
List<String> currTransactionTypes = Lists.newArrayList();
for (Row row : results) {
String agentRollupId = checkNotNull(row.getString(0));
String transactionType = checkNotNull(row.getString(1));
if (currAgentRollup == null) {
currAgentRollup = agentRollupId;
}
if (!agentRollupId.equals(currAgentRollup)) {
builder.put(currAgentRollup, ImmutableList.copyOf(currTransactionTypes));
currAgentRollup = agentRollupId;
currTransactionTypes = Lists.newArrayList();
}
currTransactionTypes.add(transactionType);
}
if (currAgentRollup != null) {
builder.put(currAgentRollup, ImmutableList.copyOf(currTransactionTypes));
}
return builder.build();
}
}
}