| /* |
| * 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); |
| } |
| } |
| } |