content: /*
   * 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.stats;
  
  import java.io.PrintStream;
  import java.util.List;
  import java.util.Map;
  
  import org.apache.cassandra.io.util.FileUtils;
  import org.apache.cassandra.schema.SchemaConstants;
  import org.apache.cassandra.utils.FBUtilities;
  
  public class TableStatsPrinter<T extends StatsHolder>
  {
      public static <T extends StatsHolder> StatsPrinter<T> from(String format, boolean sorted)
      {
          switch (format)
          {
              case "json":
                  return new StatsPrinter.JsonPrinter<T>();
              case "yaml":
                  return new StatsPrinter.YamlPrinter<T>();
              default:
                  if (sorted)
                      return (StatsPrinter<T>) new SortedDefaultPrinter();
                  else
                      return (StatsPrinter<T>) new DefaultPrinter();
          }
      }
  
      /**
       * A StatsPrinter to print stats in a keyspace-centric way, nesting stats for each table under their parent keyspaces.
       */
      private static class DefaultPrinter implements StatsPrinter<TableStatsHolder>
      {
          @Override
          public void print(TableStatsHolder data, PrintStream out)
          {
              if (data.numberOfTables == 0)
                  return;
  
              out.println("Total number of tables: " + data.numberOfTables);
              out.println("----------------");
  
              List<StatsKeyspace> keyspaces = data.keyspaces;
              for (StatsKeyspace keyspace : keyspaces)
              {
                  // print each keyspace's information
                  out.println("Keyspace: " + keyspace.name);
                  out.println("\tRead Count: " + keyspace.readCount);
                  out.println("\tRead Latency: " + FBUtilities.prettyPrintLatency(keyspace.readLatency()));
                  out.println("\tWrite Count: " + keyspace.writeCount);
                  out.println("\tWrite Latency: " + FBUtilities.prettyPrintLatency(keyspace.writeLatency()));
                  out.println("\tPending Flushes: " + keyspace.pendingFlushes);
                  out.println("\tSpace used (live): " + formatDataSize(keyspace.spaceUsedLive, data.humanReadable));
                  out.println("\tSpace used (total): " + formatDataSize(keyspace.spaceUsedTotal, data.humanReadable));
  
                  // print each table's information
                  List<StatsTable> tables = keyspace.tables;
                  if (tables.size() == 0)
                      continue;
  
                  for (StatsTable table : tables)
                  {
                      printStatsTable(table, table.tableName, "\t\t", data.humanReadable, out);
                  }
                  out.println("----------------");
              }
          }
  
          protected void printStatsTable(StatsTable table, String tableDisplayName, String indent, boolean humanReadable, PrintStream out)
          {
              out.println(indent + "Table" + (table.isIndex ? " (index): " : ": ") + tableDisplayName);
              out.println(indent + "SSTable count: " + table.sstableCount);
              out.println(indent + "Old SSTable count: " + table.oldSSTableCount);
              out.println(indent + "Max SSTable size: " + formatDataSize(table.maxSSTableSize, humanReadable));
              if (table.twcs != null)
                  out.println(indent + "SSTables Time Window: " + table.twcs);
              if (table.isLeveledSstable)
              {
                  out.println(indent + "SSTables in each level: [" + String.join(", ",
                                                                                 table.sstablesInEachLevel) + "]");
                  out.println(indent + "SSTable bytes in each level: [" + String.join(", ",
                                                                                      table.sstableBytesInEachLevel) + "]");
              }
  
              out.println(indent + "Space used (live): " + table.spaceUsedLive);
              out.println(indent + "Space used (total): " + table.spaceUsedTotal);
              out.println(indent + "Space used by snapshots (total): " + table.spaceUsedBySnapshotsTotal);
  
              if (table.offHeapUsed)
                  out.println(indent + "Off heap memory used (total): " + table.offHeapMemoryUsedTotal);
              out.println(indent + "SSTable Compression Ratio: " + FBUtilities.prettyPrintRatio(table.sstableCompressionRatio));
              out.println(indent + "Number of partitions (estimate): " + table.numberOfPartitionsEstimate);
              out.println(indent + "Memtable cell count: " + table.memtableCellCount);
              out.println(indent + "Memtable data size: " + table.memtableDataSize);
  
              if (table.memtableOffHeapUsed)
                  out.println(indent + "Memtable off heap memory used: " + table.memtableOffHeapMemoryUsed);
              out.println(indent + "Memtable switch count: " + table.memtableSwitchCount);
              out.println(indent + "Speculative retries: " + table.speculativeRetries);
              out.println(indent + "Local read count: " + table.localReadCount);
              out.println(indent + "Local read latency: " + FBUtilities.prettyPrintLatency(table.localReadLatencyMs));
              out.println(indent + "Local write count: " + table.localWriteCount);
              out.println(indent + "Local write latency: " + FBUtilities.prettyPrintLatency(table.localWriteLatencyMs));
  
              out.println(indent + "Local read/write ratio: " + FBUtilities.prettyPrintRatio(table.localReadWriteRatio));
  
              out.println(indent + "Pending flushes: " + table.pendingFlushes);
              out.println(indent + "Percent repaired: " + table.percentRepaired);
  
              out.println(indent + "Bytes repaired: " + formatDataSize(table.bytesRepaired, humanReadable));
              out.println(indent + "Bytes unrepaired: " + formatDataSize(table.bytesUnrepaired, humanReadable));
              out.println(indent + "Bytes pending repair: " + formatDataSize(table.bytesPendingRepair, humanReadable));
  
              out.println(indent + "Bloom filter false positives: " + table.bloomFilterFalsePositives);
              out.println(indent + "Bloom filter false ratio: " + FBUtilities.prettyPrintRatio(table.bloomFilterFalseRatio));
              out.println(indent + "Bloom filter space used: " + table.bloomFilterSpaceUsed);
  
              if (table.bloomFilterOffHeapUsed)
                  out.println(indent + "Bloom filter off heap memory used: " + table.bloomFilterOffHeapMemoryUsed);
              if (table.indexSummaryOffHeapUsed)
                  out.println(indent + "Index summary off heap memory used: " + table.indexSummaryOffHeapMemoryUsed);
              if (table.compressionMetadataOffHeapUsed)
                  out.println(indent + "Compression metadata off heap memory used: " + table.compressionMetadataOffHeapMemoryUsed);
  
              out.println(indent + "Compacted partition minimum bytes: " + formatDataSize(table.compactedPartitionMinimumBytes, humanReadable));
              out.println(indent + "Compacted partition maximum bytes: " + formatDataSize(table.compactedPartitionMaximumBytes, humanReadable));
              out.println(indent + "Compacted partition mean bytes: " + formatDataSize(table.compactedPartitionMeanBytes, humanReadable));
              out.println(indent + "Average live cells per slice (last five minutes): " +
                          FBUtilities.prettyPrintAverage(table.averageLiveCellsPerSliceLastFiveMinutes));
              out.println(indent + "Maximum live cells per slice (last five minutes): " + table.maximumLiveCellsPerSliceLastFiveMinutes);
              out.println(indent + "Average tombstones per slice (last five minutes): " +
                          FBUtilities.prettyPrintAverage(table.averageTombstonesPerSliceLastFiveMinutes));
              out.println(indent + "Maximum tombstones per slice (last five minutes): " + table.maximumTombstonesPerSliceLastFiveMinutes);
              out.println(indent + "Droppable tombstone ratio: " + FBUtilities.prettyPrintRatio(table.droppableTombstoneRatio));
              if (table.isInCorrectLocation != null)
                  out.println(indent + "SSTables in correct location: " + table.isInCorrectLocation);
              if (table.topSizePartitions != null && !table.topSizePartitions.isEmpty())
              {
                  out.printf(indent + "Top partitions by size (last update: %s):%n", table.topSizePartitionsLastUpdate);
                  int maxWidth = Math.max(table.topSizePartitions.keySet().stream().map(String::length).max(Integer::compareTo).get() + 3, 5);
                  out.printf(indent + "  %-" + maxWidth + "s %s%n", "Key", "Size");
                  for (Map.Entry<String, String> size : table.topSizePartitions.entrySet())
                      out.printf(indent + "  %-" + maxWidth + "s %s%n", size.getKey(), size.getValue());
              }
  
              if (table.topTombstonePartitions != null && !table.topTombstonePartitions.isEmpty())
              {
                  out.printf(indent + "Top partitions by tombstone count (last update: %s):%n", table.topTombstonePartitionsLastUpdate);
                  int maxWidth = Math.max(table.topTombstonePartitions.keySet().stream().map(String::length).max(Integer::compareTo).get() + 3, 5);
                  out.printf(indent + "  %-" + maxWidth + "s %s%n", "Key", "Count");
                  for (Map.Entry<String, Long> tombstonecnt : table.topTombstonePartitions.entrySet())
                      out.printf(indent + "  %-" + maxWidth + "s %s%n", tombstonecnt.getKey(), tombstonecnt.getValue());
              }
  
              if (!SchemaConstants.isSystemKeyspace(table.keyspaceName) && table.saiTotalIndexCount > 0)
              {
                  out.println(indent + "SAI local query latency (mean): " + FBUtilities.prettyPrintLatency(table.saiQueryLatencyMs));
                  out.println(indent + "SAI post-filtering latency (mean): " + FBUtilities.prettyPrintLatency(table.saiPostFilteringReadLatencyMs));
                  out.println(indent + "SAI space used (bytes): " + table.saiDiskUsedBytes);
                  out.println(indent + "SAI sstable indexes hit per query (mean): " + table.saiSSTableIndexesHit);
                  out.println(indent + "SAI index segments hit per query (mean): " + table.saiIndexSegmentsHit);
                  out.println(indent + "SAI rows filtered per query (mean): " + table.saiRowsFiltered);
                  out.println(indent + "SAI local query timeouts: " + table.saiTotalQueryTimeouts);
                  out.println(indent + "SAI queryable/total indexes: " + table.saiTotalQueryableIndexRatio);
              }
  
              out.println("");
          }
  
          private String formatDataSize(long bytes, boolean humanReadable)
          {
              return humanReadable ? FileUtils.stringifyFileSize(bytes) : Long.toString(bytes);
          }
      }
  
      /**
       * A StatsPrinter to print stats in a sorted, table-centric way.
       */
      private static class SortedDefaultPrinter extends DefaultPrinter
      {
          @Override
          public void print(TableStatsHolder data, PrintStream out)
          {
              List<StatsTable> tables = data.getSortedFilteredTables();
  
              if (tables.size() == 0)
                  return;
  
              String totalTablesSummary = String.format("Total number of tables: %d", data.numberOfTables);
              if (data.top > 0)
              {
                  int k = (data.top <= data.numberOfTables) ? data.top : data.numberOfTables;
                  totalTablesSummary += String.format(" (showing top %d by %s)", k, data.sortKey);
              }
              out.println(totalTablesSummary);
              out.println("----------------");
              for (StatsTable table : tables)
              {
                  printStatsTable(table, table.keyspaceName + "." + table.tableName, "\t", data.humanReadable, out);
              }
              out.println("----------------");
          }
      }
  }
  