| /* |
| * 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 java.util.*; |
| import javax.management.InstanceNotFoundException; |
| |
| import com.google.common.collect.ArrayListMultimap; |
| import io.airlift.command.Arguments; |
| import io.airlift.command.Command; |
| import io.airlift.command.Option; |
| |
| 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; |
| import org.apache.cassandra.tools.nodetool.stats.StatsHolder; |
| import org.apache.cassandra.tools.nodetool.stats.StatsKeyspace; |
| import org.apache.cassandra.tools.nodetool.stats.StatsTable; |
| import org.apache.cassandra.tools.nodetool.stats.TableStatsPrinter; |
| |
| @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. KiB, MiB, GiB, TiB") |
| private boolean humanReadable = false; |
| |
| @Option(title = "format", |
| name = {"-F", "--format"}, |
| description = "Output format (json, yaml)") |
| private String outputFormat = ""; |
| |
| @Override |
| public void execute(NodeProbe probe) |
| { |
| if (!outputFormat.isEmpty() && !"json".equals(outputFormat) && !"yaml".equals(outputFormat)) |
| { |
| throw new IllegalArgumentException("arguments for -F are json,yaml only."); |
| } |
| |
| TableStats.OptionFilter filter = new OptionFilter(ignore, tableNames); |
| ArrayListMultimap<String, ColumnFamilyStoreMBean> selectedTableMbeans = ArrayListMultimap.create(); |
| Map<String, StatsKeyspace> keyspaceStats = new HashMap<>(); |
| |
| // get a list of table stores |
| Iterator<Map.Entry<String, ColumnFamilyStoreMBean>> tableMBeans = probe.getColumnFamilyStoreMBeanProxies(); |
| |
| while (tableMBeans.hasNext()) |
| { |
| Map.Entry<String, ColumnFamilyStoreMBean> entry = tableMBeans.next(); |
| String keyspaceName = entry.getKey(); |
| ColumnFamilyStoreMBean tableProxy = entry.getValue(); |
| |
| if (filter.isKeyspaceIncluded(keyspaceName)) |
| { |
| StatsKeyspace stats = keyspaceStats.get(keyspaceName); |
| if (stats == null) |
| { |
| stats = new StatsKeyspace(probe, keyspaceName); |
| keyspaceStats.put(keyspaceName, stats); |
| } |
| stats.add(tableProxy); |
| |
| if (filter.isTableIncluded(keyspaceName, tableProxy.getTableName())) |
| selectedTableMbeans.put(keyspaceName, tableProxy); |
| } |
| } |
| |
| // make sure all specified keyspace and tables exist |
| filter.verifyKeyspaces(probe.getKeyspaces()); |
| filter.verifyTables(); |
| |
| // get metrics of keyspace |
| StatsHolder holder = new StatsHolder(); |
| for (Map.Entry<String, Collection<ColumnFamilyStoreMBean>> entry : selectedTableMbeans.asMap().entrySet()) |
| { |
| String keyspaceName = entry.getKey(); |
| Collection<ColumnFamilyStoreMBean> tables = entry.getValue(); |
| StatsKeyspace statsKeyspace = keyspaceStats.get(keyspaceName); |
| |
| // get metrics of table statistics for this keyspace |
| for (ColumnFamilyStoreMBean table : tables) |
| { |
| String tableName = table.getTableName(); |
| StatsTable statsTable = new StatsTable(); |
| statsTable.name = tableName; |
| statsTable.isIndex = tableName.contains("."); |
| statsTable.sstableCount = probe.getColumnFamilyMetric(keyspaceName, tableName, "LiveSSTableCount"); |
| int[] leveledSStables = table.getSSTableCountPerLevel(); |
| if (leveledSStables != null) |
| { |
| statsTable.isLeveledSstable = true; |
| |
| for (int level = 0; level < leveledSStables.length; level++) |
| { |
| int count = leveledSStables[level]; |
| long maxCount = 4L; // for L0 |
| if (level > 0) |
| maxCount = (long) Math.pow(10, level); |
| // show max threshold for level when exceeded |
| statsTable.sstablesInEachLevel.add(count + ((count > maxCount) ? "/" + maxCount : "")); |
| } |
| } |
| |
| Long memtableOffHeapSize = null; |
| Long bloomFilterOffHeapSize = null; |
| Long indexSummaryOffHeapSize = null; |
| Long compressionMetadataOffHeapSize = null; |
| Long offHeapSize = null; |
| Double percentRepaired = 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; |
| percentRepaired = (Double) probe.getColumnFamilyMetric(keyspaceName, tableName, "PercentRepaired"); |
| } |
| 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; |
| } |
| |
| statsTable.spaceUsedLive = format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "LiveDiskSpaceUsed"), humanReadable); |
| statsTable.spaceUsedTotal = format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "TotalDiskSpaceUsed"), humanReadable); |
| statsTable.spaceUsedBySnapshotsTotal = format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "SnapshotsSize"), humanReadable); |
| if (offHeapSize != null) |
| { |
| statsTable.offHeapUsed = true; |
| statsTable.offHeapMemoryUsedTotal = format(offHeapSize, humanReadable); |
| |
| } |
| if (percentRepaired != null) |
| { |
| statsTable.percentRepaired = Math.round(100 * percentRepaired) / 100.0; |
| } |
| statsTable.sstableCompressionRatio = probe.getColumnFamilyMetric(keyspaceName, tableName, "CompressionRatio"); |
| Object estimatedPartitionCount = probe.getColumnFamilyMetric(keyspaceName, tableName, "EstimatedPartitionCount"); |
| if (Long.valueOf(-1L).equals(estimatedPartitionCount)) |
| { |
| estimatedPartitionCount = 0L; |
| } |
| statsTable.numberOfKeysEstimate = estimatedPartitionCount; |
| |
| statsTable.memtableCellCount = probe.getColumnFamilyMetric(keyspaceName, tableName, "MemtableColumnsCount"); |
| statsTable.memtableDataSize = format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "MemtableLiveDataSize"), humanReadable); |
| if (memtableOffHeapSize != null) |
| { |
| statsTable.memtableOffHeapUsed = true; |
| statsTable.memtableOffHeapMemoryUsed = format(memtableOffHeapSize, humanReadable); |
| } |
| statsTable.memtableSwitchCount = probe.getColumnFamilyMetric(keyspaceName, tableName, "MemtableSwitchCount"); |
| statsTable.localReadCount = ((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; |
| statsTable.localReadLatencyMs = localRLatency; |
| statsTable.localWriteCount = ((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; |
| statsTable.localWriteLatencyMs = localWLatency; |
| statsTable.pendingFlushes = probe.getColumnFamilyMetric(keyspaceName, tableName, "PendingFlushes"); |
| |
| statsTable.bloomFilterFalsePositives = probe.getColumnFamilyMetric(keyspaceName, tableName, "BloomFilterFalsePositives"); |
| statsTable.bloomFilterFalseRatio = probe.getColumnFamilyMetric(keyspaceName, tableName, "RecentBloomFilterFalseRatio"); |
| statsTable.bloomFilterSpaceUsed = format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "BloomFilterDiskSpaceUsed"), humanReadable); |
| |
| if (bloomFilterOffHeapSize != null) |
| { |
| statsTable.bloomFilterOffHeapUsed = true; |
| statsTable.bloomFilterOffHeapMemoryUsed = format(bloomFilterOffHeapSize, humanReadable); |
| } |
| |
| if (indexSummaryOffHeapSize != null) |
| { |
| statsTable.indexSummaryOffHeapUsed = true; |
| statsTable.indexSummaryOffHeapMemoryUsed = format(indexSummaryOffHeapSize, humanReadable); |
| } |
| if (compressionMetadataOffHeapSize != null) |
| { |
| statsTable.compressionMetadataOffHeapUsed = true; |
| statsTable.compressionMetadataOffHeapMemoryUsed = format(compressionMetadataOffHeapSize, humanReadable); |
| } |
| statsTable.compactedPartitionMinimumBytes = (Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "MinPartitionSize"); |
| statsTable.compactedPartitionMaximumBytes = (Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "MaxPartitionSize"); |
| statsTable.compactedPartitionMeanBytes = (Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "MeanPartitionSize"); |
| |
| CassandraMetricsRegistry.JmxHistogramMBean histogram = (CassandraMetricsRegistry.JmxHistogramMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "LiveScannedHistogram"); |
| statsTable.averageLiveCellsPerSliceLastFiveMinutes = histogram.getMean(); |
| statsTable.maximumLiveCellsPerSliceLastFiveMinutes = histogram.getMax(); |
| |
| histogram = (CassandraMetricsRegistry.JmxHistogramMBean) probe.getColumnFamilyMetric(keyspaceName, tableName, "TombstoneScannedHistogram"); |
| statsTable.averageTombstonesPerSliceLastFiveMinutes = histogram.getMean(); |
| statsTable.maximumTombstonesPerSliceLastFiveMinutes = histogram.getMax(); |
| statsTable.droppedMutations = format((Long) probe.getColumnFamilyMetric(keyspaceName, tableName, "DroppedMutations"), humanReadable); |
| statsKeyspace.tables.add(statsTable); |
| } |
| holder.keyspaces.add(statsKeyspace); |
| } |
| // print out the keyspace and table statistics |
| TableStatsPrinter printer = TableStatsPrinter.from(outputFormat); |
| printer.print(holder, System.out); |
| } |
| |
| 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 final Map<String, List<String>> filter = new HashMap<>(); |
| private final Map<String, List<String>> verifier = new HashMap<>(); // Same as filter initially, but we remove tables every time we've checked them for inclusion |
| // in isTableIncluded() so that we detect if those table requested don't exist (verifyTables()) |
| private final List<String> filterList = new ArrayList<>(); |
| private final boolean ignoreMode; |
| |
| 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<>()); |
| verifier.put(keyValues[0], new ArrayList<>()); |
| } |
| |
| if (keyValues.length == 2) |
| { |
| filter.get(keyValues[0]).add(keyValues[1]); |
| verifier.get(keyValues[0]).add(keyValues[1]); |
| } |
| } |
| } |
| |
| public boolean isTableIncluded(String keyspace, String table) |
| { |
| // 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.isEmpty()) |
| return !ignoreMode; |
| |
| // keyspace exists, and it contains specific table |
| verifier.get(keyspace).remove(table); |
| return ignoreMode ^ tables.contains(table); |
| } |
| |
| public boolean isKeyspaceIncluded(String keyspace) |
| { |
| // supplying empty params list is treated as wanting to display all keyspaces and tables |
| if (filterList.isEmpty()) |
| return !ignoreMode; |
| |
| // Note that if there is any table for the keyspace, we want to include the keyspace irregarding |
| // of the ignoreMode, since the ignoreMode then apply to the table inside the keyspace but the |
| // keyspace itself is not ignored |
| return filter.get(keyspace) != null || ignoreMode; |
| } |
| |
| public void verifyKeyspaces(List<String> keyspaces) |
| { |
| for (String ks : verifier.keySet()) |
| if (!keyspaces.contains(ks)) |
| throw new IllegalArgumentException("Unknown keyspace: " + ks); |
| } |
| |
| public void verifyTables() |
| { |
| for (String ks : filter.keySet()) |
| if (!verifier.get(ks).isEmpty()) |
| throw new IllegalArgumentException("Unknown tables: " + verifier.get(ks) + " in keyspace: " + ks); |
| } |
| } |
| } |