blob: c7d0d301a6c8515e2a1b4d66fa6707399c49605a [file] [log] [blame]
/*
* 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.tools.nodetool;
import io.airlift.command.Arguments;
import io.airlift.command.Command;
import io.airlift.command.Option;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.management.InstanceNotFoundException;
import org.apache.cassandra.db.ColumnFamilyStoreMBean;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.metrics.CassandraMetricsRegistry;
import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
@Command(name = "tablestats", description = "Print statistics on tables")
public class TableStats extends NodeToolCmd
{
@Arguments(usage = "[<keyspace.table>...]", description = "List of tables (or keyspace) names")
private List<String> tableNames = new ArrayList<>();
@Option(name = "-i", description = "Ignore the list of tables and display the remaining tables")
private boolean ignore = false;
@Option(title = "human_readable",
name = {"-H", "--human-readable"},
description = "Display bytes in human readable form, i.e. KB, MB, GB, TB")
private boolean humanReadable = false;
@Override
public void execute(NodeProbe probe)
{
TableStats.OptionFilter filter = new OptionFilter(ignore, tableNames);
Map<String, List<ColumnFamilyStoreMBean>> tableStoreMap = new HashMap<>();
// get a list of column family stores
Iterator<Map.Entry<String, ColumnFamilyStoreMBean>> tables = probe.getColumnFamilyStoreMBeanProxies();
while (tables.hasNext())
{
Map.Entry<String, ColumnFamilyStoreMBean> entry = tables.next();
String keyspaceName = entry.getKey();
ColumnFamilyStoreMBean tableProxy = entry.getValue();
if (!tableStoreMap.containsKey(keyspaceName) && filter.isColumnFamilyIncluded(entry.getKey(), tableProxy.getColumnFamilyName()))
{
List<ColumnFamilyStoreMBean> columnFamilies = new ArrayList<>();
columnFamilies.add(tableProxy);
tableStoreMap.put(keyspaceName, columnFamilies);
} else if (filter.isColumnFamilyIncluded(entry.getKey(), tableProxy.getColumnFamilyName()))
{
tableStoreMap.get(keyspaceName).add(tableProxy);
}
}
// make sure all specified keyspace and tables exist
filter.verifyKeyspaces(probe.getKeyspaces());
filter.verifyColumnFamilies();
// print out the table statistics
for (Map.Entry<String, List<ColumnFamilyStoreMBean>> entry : tableStoreMap.entrySet())
{
String keyspaceName = entry.getKey();
List<ColumnFamilyStoreMBean> columnFamilies = entry.getValue();
long keyspaceReadCount = 0;
long keyspaceWriteCount = 0;
int keyspacePendingFlushes = 0;
double keyspaceTotalReadTime = 0.0f;
double keyspaceTotalWriteTime = 0.0f;
System.out.println("Keyspace: " + keyspaceName);
for (ColumnFamilyStoreMBean table : columnFamilies)
{
String tableName = table.getColumnFamilyName();
long writeCount = ((CassandraMetricsRegistry.JmxTimerMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "WriteLatency")).getCount();
long readCount = ((CassandraMetricsRegistry.JmxTimerMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "ReadLatency")).getCount();
if (readCount > 0)
{
keyspaceReadCount += readCount;
keyspaceTotalReadTime += (long) probe.getColumnFamilyMetric(keyspaceName, tableName, "ReadTotalLatency");
}
if (writeCount > 0)
{
keyspaceWriteCount += writeCount;
keyspaceTotalWriteTime += (long) probe.getColumnFamilyMetric(keyspaceName, tableName, "WriteTotalLatency");
}
keyspacePendingFlushes += (long) probe.getColumnFamilyMetric(keyspaceName, tableName, "PendingFlushes");
}
double keyspaceReadLatency = keyspaceReadCount > 0
? keyspaceTotalReadTime / keyspaceReadCount / 1000
: Double.NaN;
double keyspaceWriteLatency = keyspaceWriteCount > 0
? keyspaceTotalWriteTime / keyspaceWriteCount / 1000
: Double.NaN;
System.out.println("\tRead Count: " + keyspaceReadCount);
System.out.println("\tRead Latency: " + String.format("%s", keyspaceReadLatency) + " ms.");
System.out.println("\tWrite Count: " + keyspaceWriteCount);
System.out.println("\tWrite Latency: " + String.format("%s", keyspaceWriteLatency) + " ms.");
System.out.println("\tPending Flushes: " + keyspacePendingFlushes);
// print out column family statistics for this keyspace
for (ColumnFamilyStoreMBean table : columnFamilies)
{
String tableName = table.getColumnFamilyName();
if (tableName.contains("."))
System.out.println("\t\tTable (index): " + tableName);
else
System.out.println("\t\tTable: " + tableName);
System.out.println("\t\tSSTable count: " + probe.getColumnFamilyMetric(keyspaceName, tableName, "LiveSSTableCount"));
int[] leveledSStables = table.getSSTableCountPerLevel();
if (leveledSStables != null)
{
System.out.print("\t\tSSTables in each level: [");
for (int level = 0; level < leveledSStables.length; level++)
{
int count = leveledSStables[level];
System.out.print(count);
long maxCount = 4L; // for L0
if (level > 0)
maxCount = (long) Math.pow(10, level);
// show max threshold for level when exceeded
if (count > maxCount)
System.out.print("/" + maxCount);
if (level < leveledSStables.length - 1)
System.out.print(", ");
else
System.out.println("]");
}
}
Long memtableOffHeapSize = null;
Long bloomFilterOffHeapSize = null;
Long indexSummaryOffHeapSize = null;
Long compressionMetadataOffHeapSize = null;
Long offHeapSize = null;
try
{
memtableOffHeapSize = (Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "MemtableOffHeapSize");
bloomFilterOffHeapSize = (Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "BloomFilterOffHeapMemoryUsed");
indexSummaryOffHeapSize = (Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "IndexSummaryOffHeapMemoryUsed");
compressionMetadataOffHeapSize = (Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "CompressionMetadataOffHeapMemoryUsed");
offHeapSize = memtableOffHeapSize + bloomFilterOffHeapSize + indexSummaryOffHeapSize + compressionMetadataOffHeapSize;
}
catch (RuntimeException e)
{
// offheap-metrics introduced in 2.1.3 - older versions do not have the appropriate mbeans
if (!(e.getCause() instanceof InstanceNotFoundException))
throw e;
}
System.out.println("\t\tSpace used (live): " + format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "LiveDiskSpaceUsed"), humanReadable));
System.out.println("\t\tSpace used (total): " + format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "TotalDiskSpaceUsed"), humanReadable));
System.out.println("\t\tSpace used by snapshots (total): " + format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "SnapshotsSize"), humanReadable));
if (offHeapSize != null)
System.out.println("\t\tOff heap memory used (total): " + format(offHeapSize, humanReadable));
System.out.println("\t\tSSTable Compression Ratio: " + probe.getColumnFamilyMetric(keyspaceName, tableName, "CompressionRatio"));
Object estimatedPartitionCount = probe.getColumnFamilyMetric(keyspaceName, tableName, "EstimatedPartitionCount");
if (Long.valueOf(-1L).equals(estimatedPartitionCount))
{
estimatedPartitionCount = 0L;
}
System.out.println("\t\tNumber of partitions (estimate): " + estimatedPartitionCount);
System.out.println("\t\tMemtable cell count: " + probe.getColumnFamilyMetric(keyspaceName, tableName, "MemtableColumnsCount"));
System.out.println("\t\tMemtable data size: " + format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "MemtableLiveDataSize"), humanReadable));
if (memtableOffHeapSize != null)
System.out.println("\t\tMemtable off heap memory used: " + format(memtableOffHeapSize, humanReadable));
System.out.println("\t\tMemtable switch count: " + probe.getColumnFamilyMetric(keyspaceName, tableName, "MemtableSwitchCount"));
System.out.println("\t\tLocal read count: " + ((CassandraMetricsRegistry.JmxTimerMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "ReadLatency")).getCount());
double localReadLatency = ((CassandraMetricsRegistry.JmxTimerMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "ReadLatency")).getMean() / 1000;
double localRLatency = localReadLatency > 0 ? localReadLatency : Double.NaN;
System.out.printf("\t\tLocal read latency: %01.3f ms%n", localRLatency);
System.out.println("\t\tLocal write count: " + ((CassandraMetricsRegistry.JmxTimerMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "WriteLatency")).getCount());
double localWriteLatency = ((CassandraMetricsRegistry.JmxTimerMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "WriteLatency")).getMean() / 1000;
double localWLatency = localWriteLatency > 0 ? localWriteLatency : Double.NaN;
System.out.printf("\t\tLocal write latency: %01.3f ms%n", localWLatency);
System.out.println("\t\tPending flushes: " + probe.getColumnFamilyMetric(keyspaceName, tableName, "PendingFlushes"));
System.out.println("\t\tBloom filter false positives: " + probe.getColumnFamilyMetric(keyspaceName, tableName, "BloomFilterFalsePositives"));
System.out.printf("\t\tBloom filter false ratio: %s%n", String.format("%01.5f", probe.getColumnFamilyMetric(keyspaceName, tableName, "RecentBloomFilterFalseRatio")));
System.out.println("\t\tBloom filter space used: " + format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "BloomFilterDiskSpaceUsed"), humanReadable));
if (bloomFilterOffHeapSize != null)
System.out.println("\t\tBloom filter off heap memory used: " + format(bloomFilterOffHeapSize, humanReadable));
if (indexSummaryOffHeapSize != null)
System.out.println("\t\tIndex summary off heap memory used: " + format(indexSummaryOffHeapSize, humanReadable));
if (compressionMetadataOffHeapSize != null)
System.out.println("\t\tCompression metadata off heap memory used: " + format(compressionMetadataOffHeapSize, humanReadable));
System.out.println("\t\tCompacted partition minimum bytes: " + format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "MinPartitionSize"), humanReadable));
System.out.println("\t\tCompacted partition maximum bytes: " + format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "MaxPartitionSize"), humanReadable));
System.out.println("\t\tCompacted partition mean bytes: " + format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "MeanPartitionSize"), humanReadable));
CassandraMetricsRegistry.JmxHistogramMBean histogram = (CassandraMetricsRegistry.JmxHistogramMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "LiveScannedHistogram");
System.out.println("\t\tAverage live cells per slice (last five minutes): " + histogram.getMean());
System.out.println("\t\tMaximum live cells per slice (last five minutes): " + histogram.getMax());
histogram = (CassandraMetricsRegistry.JmxHistogramMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "TombstoneScannedHistogram");
System.out.println("\t\tAverage tombstones per slice (last five minutes): " + histogram.getMean());
System.out.println("\t\tMaximum tombstones per slice (last five minutes): " + histogram.getMax());
System.out.println("");
}
System.out.println("----------------");
}
}
private String format(long bytes, boolean humanReadable) {
return humanReadable ? FileUtils.stringifyFileSize(bytes) : Long.toString(bytes);
}
/**
* Used for filtering keyspaces and tables to be displayed using the tablestats command.
*/
private static class OptionFilter
{
private Map<String, List<String>> filter = new HashMap<>();
private Map<String, List<String>> verifier = new HashMap<>();
private List<String> filterList = new ArrayList<>();
private boolean ignoreMode;
public OptionFilter(boolean ignoreMode, List<String> filterList)
{
this.filterList.addAll(filterList);
this.ignoreMode = ignoreMode;
for (String s : filterList)
{
String[] keyValues = s.split("\\.", 2);
// build the map that stores the keyspaces and tables to use
if (!filter.containsKey(keyValues[0]))
{
filter.put(keyValues[0], new ArrayList<String>());
verifier.put(keyValues[0], new ArrayList<String>());
if (keyValues.length == 2)
{
filter.get(keyValues[0]).add(keyValues[1]);
verifier.get(keyValues[0]).add(keyValues[1]);
}
} else
{
if (keyValues.length == 2)
{
filter.get(keyValues[0]).add(keyValues[1]);
verifier.get(keyValues[0]).add(keyValues[1]);
}
}
}
}
public boolean isColumnFamilyIncluded(String keyspace, String columnFamily)
{
// supplying empty params list is treated as wanting to display all keyspaces and tables
if (filterList.isEmpty())
return !ignoreMode;
List<String> tables = filter.get(keyspace);
// no such keyspace is in the map
if (tables == null)
return ignoreMode;
// only a keyspace with no tables was supplied
// so ignore or include (based on the flag) every column family in specified keyspace
else if (tables.size() == 0)
return !ignoreMode;
// keyspace exists, and it contains specific table
verifier.get(keyspace).remove(columnFamily);
return ignoreMode ^ tables.contains(columnFamily);
}
public void verifyKeyspaces(List<String> keyspaces)
{
for (String ks : verifier.keySet())
if (!keyspaces.contains(ks))
throw new IllegalArgumentException("Unknown keyspace: " + ks);
}
public void verifyColumnFamilies()
{
for (String ks : filter.keySet())
if (verifier.get(ks).size() > 0)
throw new IllegalArgumentException("Unknown tables: " + verifier.get(ks) + " in keyspace: " + ks);
}
}
}