blob: 8a254e25aa4a6df4b7b488097e5ed3b523a9a701 [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;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.TreeMap;
import java.util.function.LongFunction;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.StatsComponent;
import org.apache.cassandra.io.sstable.metadata.MetadataType;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.EstimatedHistogram;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.streamhist.TombstoneHistogram;
import static java.lang.String.format;
@SuppressWarnings("serial")
public final class Util
{
static final String RESET = "\u001B[0m";
static final String BLUE = "\u001B[34m";
static final String CYAN = "\u001B[36m";
static final String WHITE = "\u001B[37m";
private static final List<String> ANSI_COLORS = Lists.newArrayList(RESET, BLUE, CYAN, WHITE);
private static final String FULL_BAR_UNICODE = Strings.repeat("\u2593", 30);
private static final String EMPTY_BAR_UNICODE = Strings.repeat("\u2591", 30);
private static final String FULL_BAR_ASCII = Strings.repeat("#", 30);
private static final String EMPTY_BAR_ASCII = Strings.repeat("-", 30);
private static final TreeMap<Double, String> BARS_UNICODE = new TreeMap<Double, String>()
{{
this.put(1.0, "\u2589"); // full, actually using 7/8th for bad font impls of fullblock
this.put(7.0 / 8.0, "\u2589"); // 7/8ths left block
this.put(3.0 / 4.0, "\u258A"); // 3/4th block
this.put(5.0 / 8.0, "\u258B"); // 5/8th
this.put(3.0 / 8.0, "\u258D"); // three eighths, skips 1/2 due to font inconsistencies
this.put(1.0 / 4.0, "\u258E"); // 1/4th
this.put(1.0 / 8.0, "\u258F"); // 1/8th
}};
private static final TreeMap<Double, String> BARS_ASCII = new TreeMap<Double, String>()
{{
this.put(1.00, "O");
this.put(0.75, "o");
this.put(0.30, ".");
}};
private static TreeMap<Double, String> barmap(boolean unicode)
{
return unicode ? BARS_UNICODE : BARS_ASCII;
}
public static String progress(double percentComplete, int width, boolean unicode)
{
assert percentComplete >= 0 && percentComplete <= 1;
int cols = (int) (percentComplete * width);
return (unicode ? FULL_BAR_UNICODE : FULL_BAR_ASCII).substring(width - cols) +
(unicode ? EMPTY_BAR_UNICODE : EMPTY_BAR_ASCII ).substring(cols);
}
public static String stripANSI(String string)
{
return ANSI_COLORS.stream().reduce(string, (a, b) -> a.replace(b, ""));
}
public static int countANSI(String string)
{
return string.length() - stripANSI(string).length();
}
public static String wrapQuiet(String toWrap, boolean color)
{
if (Strings.isNullOrEmpty(toWrap))
{
return "";
}
StringBuilder sb = new StringBuilder();
if (color) sb.append(WHITE);
sb.append("(");
sb.append(toWrap);
sb.append(")");
if (color) sb.append(RESET);
return sb.toString();
}
public static class TermHistogram
{
public long max;
public long min;
public double sum;
int maxCountLength = 5;
int maxOffsetLength = 5;
Map<? extends Number, Long> histogram;
LongFunction<String> offsetName;
LongFunction<String> countName;
String title;
public TermHistogram(Map<? extends Number, Long> histogram,
String title,
LongFunction<String> offsetName,
LongFunction<String> countName)
{
this.offsetName = offsetName;
this.countName = countName;
this.histogram = histogram;
this.title = title;
maxOffsetLength = title.length();
histogram.entrySet().stream().forEach(e ->
{
max = Math.max(max, e.getValue());
min = Math.min(min, e.getValue());
sum += e.getValue();
// find max width, but remove ansi sequences first
maxCountLength = Math.max(maxCountLength, stripANSI(countName.apply(e.getValue())).length());
maxOffsetLength = Math.max(maxOffsetLength, stripANSI(offsetName.apply(e.getKey().longValue())).length());
});
}
public TermHistogram(TombstoneHistogram histogram,
String title,
LongFunction<String> offsetName,
LongFunction<String> countName)
{
this(new TreeMap<Number, Long>()
{
{
histogram.forEach((point, value) -> {
this.put(point, (long) value);
});
}
}, title, offsetName, countName);
}
public TermHistogram(EstimatedHistogram histogram,
String title,
LongFunction<String> offsetName,
LongFunction<String> countName)
{
this(new TreeMap<Number, Long>()
{
{
long[] counts = histogram.getBuckets(false);
long[] offsets = histogram.getBucketOffsets();
for (int i = 0; i < counts.length; i++)
{
long e = counts[i];
if (e > 0)
{
put(offsets[i], e);
}
}
}
}, title, offsetName, countName);
}
public String bar(long count, int length, String color, boolean unicode)
{
if (color == null) color = "";
StringBuilder sb = new StringBuilder(color);
long barVal = count;
int intWidth = (int) (barVal * 1.0 / max * length);
double remainderWidth = (barVal * 1.0 / max * length) - intWidth;
sb.append(Strings.repeat(barmap(unicode).get(1.0), intWidth));
if (barmap(unicode).floorKey(remainderWidth) != null)
sb.append(barmap(unicode).get(barmap(unicode).floorKey(remainderWidth)));
if(!Strings.isNullOrEmpty(color))
sb.append(RESET);
return sb.toString();
}
public void printHistogram(PrintStream out, boolean color, boolean unicode)
{
// String.format includes ansi sequences in the count, so need to modify the lengths
int offsetTitleLength = color ? maxOffsetLength + BLUE.length() : maxOffsetLength;
out.printf(" %-" + offsetTitleLength + "s %s %-" + maxCountLength + "s %s %sHistogram%s %n",
color ? BLUE + title : title,
color ? CYAN + "|" + BLUE : "|",
"Count",
wrapQuiet("%", color),
color ? BLUE : "",
color ? RESET : "");
histogram.entrySet().stream().forEach(e ->
{
String offset = offsetName.apply(e.getKey().longValue());
long count = e.getValue();
String histo = bar(count, 30, color? WHITE : null, unicode);
int mol = color ? maxOffsetLength + countANSI(offset) : maxOffsetLength;
int mcl = color ? maxCountLength + countANSI(countName.apply(count)) : maxCountLength;
out.printf(" %-" + mol + "s %s %" + mcl + "s %s %s%n",
offset,
color ? CYAN + "|" + RESET : "|",
countName.apply(count),
wrapQuiet(String.format("%3s", (int) (100 * ((double) count / sum))), color),
histo);
});
EstimatedHistogram eh = new EstimatedHistogram(165);
for (Entry<? extends Number, Long> e : histogram.entrySet())
{
eh.add(e.getKey().longValue(), e.getValue());
}
String[] percentiles = new String[]{"50th", "75th", "95th", "98th", "99th", "Min", "Max"};
long[] data = new long[]
{
eh.percentile(.5),
eh.percentile(.75),
eh.percentile(.95),
eh.percentile(.98),
eh.percentile(.99),
eh.min(),
eh.max(),
};
out.println((color ? BLUE : "") + " Percentiles" + (color ? RESET : ""));
for (int i = 0; i < percentiles.length; i++)
{
out.println(format(" %s%-10s%s%s",
(color ? BLUE : ""),
percentiles[i],
(color ? RESET : ""),
offsetName.apply(data[i])));
}
}
}
private Util()
{
}
/**
* This is used by standalone tools to force static initialization of DatabaseDescriptor, and fail if configuration
* is bad.
*/
public static void initDatabaseDescriptor()
{
try
{
DatabaseDescriptor.toolInitialization();
}
catch (Throwable e)
{
boolean logStackTrace = !(e instanceof ConfigurationException) || ((ConfigurationException) e).logStackTrace;
System.out.println("Exception (" + e.getClass().getName() + ") encountered during startup: " + e.getMessage());
if (logStackTrace)
{
e.printStackTrace();
System.exit(3);
}
else
{
System.err.println(e.getMessage());
System.exit(3);
}
}
}
public static <T> Stream<T> iterToStream(Iterator<T> iter)
{
Spliterator<T> splititer = Spliterators.spliteratorUnknownSize(iter, Spliterator.IMMUTABLE);
return StreamSupport.stream(splititer, false);
}
/**
* Construct table schema from info stored in SSTable's Stats.db
*
* @param desc SSTable's descriptor
* @return Restored CFMetaData
* @throws IOException when Stats.db cannot be read
*/
public static TableMetadata metadataFromSSTable(Descriptor desc) throws IOException
{
if (!desc.version.isCompatible())
throw new IOException("Unsupported SSTable version " + desc.getFormat().name() + "/" + desc.version);
StatsComponent statsComponent = StatsComponent.load(desc, MetadataType.STATS, MetadataType.HEADER);
SerializationHeader.Component header = statsComponent.serializationHeader();
IPartitioner partitioner = FBUtilities.newPartitioner(desc);
TableMetadata.Builder builder = TableMetadata.builder("keyspace", "table").partitioner(partitioner);
header.getStaticColumns().entrySet().stream()
.forEach(entry -> {
ColumnIdentifier ident = ColumnIdentifier.getInterned(UTF8Type.instance.getString(entry.getKey()), true);
builder.addStaticColumn(ident, entry.getValue());
});
header.getRegularColumns().entrySet().stream()
.forEach(entry -> {
ColumnIdentifier ident = ColumnIdentifier.getInterned(UTF8Type.instance.getString(entry.getKey()), true);
builder.addRegularColumn(ident, entry.getValue());
});
builder.addPartitionKeyColumn("PartitionKey", header.getKeyType());
for (int i = 0; i < header.getClusteringTypes().size(); i++)
{
builder.addClusteringColumn("clustering" + (i > 0 ? i : ""), header.getClusteringTypes().get(i));
}
return builder.build();
}
}