blob: 9ff421c188af9bba43201bead93c91f06d747490 [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.db.virtual;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import org.apache.commons.math3.util.Precision;
import com.codahale.metrics.Counting;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Metered;
import com.codahale.metrics.Metric;
import com.codahale.metrics.Sampling;
import com.codahale.metrics.Snapshot;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.DoubleType;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.metrics.TableMetrics;
import org.apache.cassandra.schema.TableMetadata;
/**
* Contains multiple the Table Metric virtual tables. This is not a direct wrapper over the Metrics like with JMX but a
* view to the metrics so that the underlying mechanism can change but still give same appearance (like nodetool).
*/
public class TableMetricTables
{
private final static String KEYSPACE_NAME = "keyspace_name";
private final static String TABLE_NAME = "table_name";
private final static String P50 = "p50th";
private final static String P99 = "p99th";
private final static String MAX = "max";
private final static String RATE = "per_second";
private final static double BYTES_TO_MIB = 1.0 / (1024 * 1024);
private final static double NS_TO_MS = 0.000001;
private final static AbstractType<?> TYPE = CompositeType.getInstance(UTF8Type.instance,
UTF8Type.instance);
private final static IPartitioner PARTITIONER = new LocalPartitioner(TYPE);
/**
* Generates all table metric tables in a collection
*/
public static Collection<VirtualTable> getAll(String name)
{
return ImmutableList.of(
new LatencyTableMetric(name, "local_read_latency", t -> t.readLatency.latency),
new LatencyTableMetric(name, "local_scan_latency", t -> t.rangeLatency.latency),
new LatencyTableMetric(name, "coordinator_read_latency", t -> t.coordinatorReadLatency),
new LatencyTableMetric(name, "coordinator_scan_latency", t -> t.coordinatorScanLatency),
new LatencyTableMetric(name, "local_write_latency", t -> t.writeLatency.latency),
new LatencyTableMetric(name, "coordinator_write_latency", t -> t.coordinatorWriteLatency),
new HistogramTableMetric(name, "tombstones_per_read", t -> t.tombstoneScannedHistogram.cf),
new HistogramTableMetric(name, "rows_per_read", t -> t.liveScannedHistogram.cf),
new StorageTableMetric(name, "disk_usage", (TableMetrics t) -> t.totalDiskSpaceUsed),
new StorageTableMetric(name, "max_partition_size", (TableMetrics t) -> t.maxPartitionSize));
}
/**
* A table that describes a some amount of disk on space in a Counter or Gauge
*/
private static class StorageTableMetric extends TableMetricTable
{
interface GaugeFunction extends Function<TableMetrics, Gauge<Long>> {}
interface CountingFunction<M extends Metric & Counting> extends Function<TableMetrics, M> {}
<M extends Metric & Counting> StorageTableMetric(String keyspace, String table, CountingFunction<M> func)
{
super(keyspace, table, func, "mebibytes", LongType.instance, "");
}
StorageTableMetric(String keyspace, String table, GaugeFunction func)
{
super(keyspace, table, func, "mebibytes", LongType.instance, "");
}
/**
* Convert bytes to mebibytes, always round up to nearest MiB
*/
public void add(SimpleDataSet result, String column, long value)
{
result.column(column, (long) Math.ceil(value * BYTES_TO_MIB));
}
}
/**
* A table that describes a Latency metric, specifically a Timer
*/
private static class HistogramTableMetric extends TableMetricTable
{
<M extends Metric & Sampling> HistogramTableMetric(String keyspace, String table, Function<TableMetrics, M> func)
{
this(keyspace, table, func, "");
}
<M extends Metric & Sampling> HistogramTableMetric(String keyspace, String table, Function<TableMetrics, M> func, String suffix)
{
super(keyspace, table, func, "count", LongType.instance, suffix);
}
/**
* When displaying in cqlsh if we allow doubles to be too precise we get scientific notation which is hard to
* read so round off at 0.000.
*/
public void add(SimpleDataSet result, String column, double value)
{
result.column(column, Precision.round(value, 3, BigDecimal.ROUND_HALF_UP));
}
}
/**
* A table that describes a Latency metric, specifically a Timer
*/
private static class LatencyTableMetric extends HistogramTableMetric
{
<M extends Metric & Sampling> LatencyTableMetric(String keyspace, String table, Function<TableMetrics, M> func)
{
super(keyspace, table, func, "_ms");
}
/**
* For the metrics that are time based, convert to to milliseconds
*/
public void add(SimpleDataSet result, String column, double value)
{
if (column.endsWith(suffix))
value *= NS_TO_MS;
super.add(result, column, value);
}
}
/**
* Abstraction over the Metrics Gauge, Counter, and Timer that will turn it into a (keyspace_name, table_name)
* table.
*/
private static class TableMetricTable extends AbstractVirtualTable
{
final Function<TableMetrics, ? extends Metric> func;
final String columnName;
final String suffix;
TableMetricTable(String keyspace, String table, Function<TableMetrics, ? extends Metric> func,
String colName, AbstractType colType, String suffix)
{
super(buildMetadata(keyspace, table, func, colName, colType, suffix));
this.func = func;
this.columnName = colName;
this.suffix = suffix;
}
public void add(SimpleDataSet result, String column, double value)
{
result.column(column, value);
}
public void add(SimpleDataSet result, String column, long value)
{
result.column(column, value);
}
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
// Iterate over all tables and get metric by function
for (ColumnFamilyStore cfs : ColumnFamilyStore.all())
{
Metric metric = func.apply(cfs.metric);
// set new partition for this table
result.row(cfs.keyspace.getName(), cfs.name);
// extract information by metric type and put it in row based on implementation of `add`
if (metric instanceof Counting)
{
add(result, columnName, ((Counting) metric).getCount());
if (metric instanceof Sampling)
{
Sampling histo = (Sampling) metric;
Snapshot snapshot = histo.getSnapshot();
// EstimatedHistogram keeping them in ns is hard to parse as a human so convert to ms
add(result, P50 + suffix, snapshot.getMedian());
add(result, P99 + suffix, snapshot.get99thPercentile());
add(result, MAX + suffix, (double) snapshot.getMax());
}
if (metric instanceof Metered)
{
Metered timer = (Metered) metric;
add(result, RATE, timer.getFiveMinuteRate());
}
}
else if (metric instanceof Gauge)
{
add(result, columnName, (long) ((Gauge) metric).getValue());
}
}
return result;
}
}
/**
* Identify the type of Metric it is (gauge, counter etc) abd create the TableMetadata. The column name
* and type for a counter/gauge is formatted differently based on the units (bytes/time) so allowed to
* be set.
*/
private static TableMetadata buildMetadata(String keyspace, String table, Function<TableMetrics, ? extends Metric> func,
String colName, AbstractType colType, String suffix)
{
TableMetadata.Builder metadata = TableMetadata.builder(keyspace, table)
.kind(TableMetadata.Kind.VIRTUAL)
.addPartitionKeyColumn(KEYSPACE_NAME, UTF8Type.instance)
.addPartitionKeyColumn(TABLE_NAME, UTF8Type.instance)
.partitioner(PARTITIONER);
// get a table from system keyspace and get metric from it for determining type of metric
Keyspace system = Keyspace.system().iterator().next();
Metric test = func.apply(system.getColumnFamilyStores().iterator().next().metric);
if (test instanceof Counting)
{
metadata.addRegularColumn(colName, colType);
// if it has a Histogram include some information about distribution
if (test instanceof Sampling)
{
metadata.addRegularColumn(P50 + suffix, DoubleType.instance)
.addRegularColumn(P99 + suffix, DoubleType.instance)
.addRegularColumn(MAX + suffix, DoubleType.instance);
}
if (test instanceof Metered)
{
metadata.addRegularColumn(RATE, DoubleType.instance);
}
}
else if (test instanceof Gauge)
{
metadata.addRegularColumn(colName, colType);
}
return metadata.build();
}
}