Repository: cassandra Updated Branches: refs/heads/cassandra-2.1 e25d94e6e -> 681c380b5
Metrics for prepared stmt usage and eviction Patch by Robbie Strickland; review by Tyler Hobbs for CASSANDRA-7930 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/19c6cc19 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/19c6cc19 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/19c6cc19 Branch: refs/heads/cassandra-2.1 Commit: 19c6cc1982d2146a99ccaf6dccc087fe88d5785f Parents: 169ec3d Author: Robbie Strickland <rostrickl...@gmail.com> Authored: Tue Sep 16 13:07:52 2014 -0500 Committer: Tyler Hobbs <ty...@datastax.com> Committed: Tue Sep 16 13:07:52 2014 -0500 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../apache/cassandra/cql3/QueryProcessor.java | 47 ++++++++++++++++- .../cassandra/metrics/CqlStatementMetrics.java | 54 ++++++++++++++++++++ .../apache/cassandra/service/ClientState.java | 2 +- 4 files changed, 101 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/19c6cc19/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 3ee938a..cf7112c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.0.11: + * Add metrics for prepared statement usage and eviction (CASSANDRA-7930) * Make CQLSSTableWriter sync within partitions (CASSANDRA-7360) * Potentially use non-local replicas in CqlConfigHelper (CASSANDRA-7906) * Explicitly disallowing mixing multi-column and single-column http://git-wip-us.apache.org/repos/asf/cassandra/blob/19c6cc19/src/java/org/apache/cassandra/cql3/QueryProcessor.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index a59fe9b..ee188a3 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -19,11 +19,14 @@ package org.apache.cassandra.cql3; import java.nio.ByteBuffer; import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import com.google.common.primitives.Ints; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import com.googlecode.concurrentlinkedhashmap.EntryWeigher; +import com.googlecode.concurrentlinkedhashmap.EvictionListener; import org.antlr.runtime.*; import org.github.jamm.MemoryMeter; import org.slf4j.Logger; @@ -32,8 +35,10 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.cql3.statements.*; import org.apache.cassandra.db.*; import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.metrics.CqlStatementMetrics; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.thrift.ThriftClientState; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.transport.messages.ResultMessage; @@ -73,6 +78,9 @@ public class QueryProcessor implements QueryHandler private static final ConcurrentLinkedHashMap<MD5Digest, CQLStatement> preparedStatements; private static final ConcurrentLinkedHashMap<Integer, CQLStatement> thriftPreparedStatements; + public static final CqlStatementMetrics metrics = new CqlStatementMetrics(); + private static AtomicLong evictionCount = new AtomicLong(0); + static { if (MemoryMeter.isInitialized()) @@ -80,11 +88,29 @@ public class QueryProcessor implements QueryHandler preparedStatements = new ConcurrentLinkedHashMap.Builder<MD5Digest, CQLStatement>() .maximumWeightedCapacity(MAX_CACHE_PREPARED_MEMORY) .weigher(cqlMemoryUsageWeigher) - .build(); + .listener(new EvictionListener<MD5Digest, CQLStatement>() + { + @Override + public void onEviction(MD5Digest md5Digest, CQLStatement prepared) + { + metrics.activePreparedStatements.dec(); + metrics.evictedPreparedStatements.inc(); + evictionCount.incrementAndGet(); + } + }).build(); thriftPreparedStatements = new ConcurrentLinkedHashMap.Builder<Integer, CQLStatement>() .maximumWeightedCapacity(MAX_CACHE_PREPARED_MEMORY) .weigher(thriftMemoryUsageWeigher) - .build(); + .listener(new EvictionListener<Integer, CQLStatement>() + { + @Override + public void onEviction(Integer i, CQLStatement prepared) + { + metrics.activePreparedStatements.dec(); + metrics.evictedPreparedStatements.inc(); + evictionCount.incrementAndGet(); + } + }).build(); } else { @@ -97,6 +123,17 @@ public class QueryProcessor implements QueryHandler .maximumWeightedCapacity(MAX_CACHE_PREPARED_COUNT) .build(); } + + StorageService.scheduledTasks.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + long count = evictionCount.getAndSet(0); + if (count > 0) + { + logger.info("{} prepared statements discarded in the last minute because cache limit reached (cache limit = {} bytes)", count, MAX_CACHE_PREPARED_MEMORY); + } + } + }, 1, 1, TimeUnit.MINUTES); } private QueryProcessor() @@ -172,6 +209,9 @@ public class QueryProcessor implements QueryHandler if (prepared.getBoundTerms() != options.getValues().size()) throw new InvalidRequestException("Invalid amount of bind variables"); + if (!queryState.getClientState().isInternal) + metrics.executedUnprepared.inc(); + return processStatement(prepared, queryState, options); } @@ -271,6 +311,7 @@ public class QueryProcessor implements QueryHandler { int statementId = toHash.hashCode(); thriftPreparedStatements.put(statementId, prepared.statement); + metrics.activePreparedStatements.inc(); logger.trace(String.format("Stored prepared statement #%d with %d bind markers", statementId, prepared.statement.getBoundTerms())); @@ -280,6 +321,7 @@ public class QueryProcessor implements QueryHandler { MD5Digest statementId = MD5Digest.compute(toHash); preparedStatements.put(statementId, prepared.statement); + metrics.activePreparedStatements.inc(); logger.trace(String.format("Stored prepared statement %s with %d bind markers", statementId, prepared.statement.getBoundTerms())); @@ -306,6 +348,7 @@ public class QueryProcessor implements QueryHandler logger.trace("[{}] '{}'", i+1, variables.get(i)); } + metrics.executedPrepared.inc(); return processStatement(statement, queryState, options); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/19c6cc19/src/java/org/apache/cassandra/metrics/CqlStatementMetrics.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/metrics/CqlStatementMetrics.java b/src/java/org/apache/cassandra/metrics/CqlStatementMetrics.java new file mode 100644 index 0000000..ba27d89 --- /dev/null +++ b/src/java/org/apache/cassandra/metrics/CqlStatementMetrics.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cassandra.metrics; + +import com.yammer.metrics.Metrics; +import com.yammer.metrics.core.Counter; +import com.yammer.metrics.core.Gauge; +import com.yammer.metrics.util.RatioGauge; + + +public class CqlStatementMetrics +{ + private final MetricNameFactory factory = new DefaultNameFactory("CqlStatement"); + public final Counter activePreparedStatements = Metrics.newCounter(factory.createMetricName("ActivePreparedStatements")); + public final Counter evictedPreparedStatements = Metrics.newCounter(factory.createMetricName("EvictedPreparedStatements")); + public final Counter executedPrepared = Metrics.newCounter(factory.createMetricName("ExecutedPrepared")); + public final Counter executedUnprepared = Metrics.newCounter(factory.createMetricName("ExecutedUnPrepared")); + + public final Gauge<Double> preparedRatio = Metrics.newGauge(factory.createMetricName("PreparedUnpreparedRatio"), new RatioGauge() + { + protected double getNumerator() + { + long num = executedPrepared.count(); + return num == 0 ? 1 : num; + } + + protected double getDenominator() + { + long den = executedUnprepared.count(); + return den == 0 ? 1 : den; + } + }); + + public void reset() + { + executedPrepared.clear(); + executedUnprepared.clear(); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/19c6cc19/src/java/org/apache/cassandra/service/ClientState.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/ClientState.java b/src/java/org/apache/cassandra/service/ClientState.java index be3b895..c14540d 100644 --- a/src/java/org/apache/cassandra/service/ClientState.java +++ b/src/java/org/apache/cassandra/service/ClientState.java @@ -102,7 +102,7 @@ public class ClientState // isInternal is used to mark ClientState as used by some internal component // that should have an ability to modify system keyspace. - private final boolean isInternal; + public final boolean isInternal; // The remote address of the client - null for internal clients. private final SocketAddress remoteAddress;