package org.apache.cassandra.stress.settings; /* * * 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. * */ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.util.*; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.stress.generate.Distribution; import org.apache.cassandra.stress.generate.DistributionFactory; import org.apache.cassandra.stress.generate.DistributionFixed; import org.apache.cassandra.stress.util.ResultLogger; import org.apache.cassandra.utils.ByteBufferUtil; /** * For parsing column options */ public class SettingsColumn implements Serializable { public final int maxColumnsPerKey; public transient List<ByteBuffer> names; public final List<String> namestrs; public final String comparator; public final String timestamp; public final boolean variableColumnCount; public final boolean slice; public final DistributionFactory sizeDistribution; public final DistributionFactory countDistribution; public SettingsColumn(GroupedOptions options) { this((Options) options, options instanceof NameOptions ? (NameOptions) options : null, options instanceof CountOptions ? (CountOptions) options : null ); } public SettingsColumn(Options options, NameOptions name, CountOptions count) { sizeDistribution = options.size.get(); { timestamp = options.timestamp.value(); comparator = options.comparator.value(); AbstractType parsed = null; try { parsed = TypeParser.parse(comparator); } catch (Exception e) { System.err.println(e.getMessage()); System.exit(1); } if (!(parsed instanceof TimeUUIDType || parsed instanceof AsciiType || parsed instanceof UTF8Type)) { System.err.println("Currently supported types are: TimeUUIDType, AsciiType, UTF8Type."); System.exit(1); } } if (name != null) { assert count == null; AbstractType comparator; try { comparator = TypeParser.parse(this.comparator); } catch (Exception e) { throw new IllegalArgumentException(this.comparator + " is not a valid type"); } final String[] names = name.name.value().split(","); this.names = new ArrayList<>(names.length); for (String columnName : names) this.names.add(comparator.fromString(columnName)); Collections.sort(this.names, BytesType.instance); this.namestrs = new ArrayList<>(); for (ByteBuffer columnName : this.names) this.namestrs.add(comparator.getString(columnName)); final int nameCount = this.names.size(); countDistribution = new DistributionFactory() { @Override public Distribution get() { return new DistributionFixed(nameCount); } @Override public String getConfigAsString(){return String.format("Count: fixed=%d", nameCount);} }; } else { this.countDistribution = count.count.get(); ByteBuffer[] names = new ByteBuffer[(int) countDistribution.get().maxValue()]; String[] namestrs = new String[(int) countDistribution.get().maxValue()]; for (int i = 0 ; i < names.length ; i++) names[i] = ByteBufferUtil.bytes("C" + i); Arrays.sort(names, BytesType.instance); try { for (int i = 0 ; i < names.length ; i++) namestrs[i] = ByteBufferUtil.string(names[i]); } catch (CharacterCodingException e) { throw new RuntimeException(e); } this.names = Arrays.asList(names); this.namestrs = Arrays.asList(namestrs); } maxColumnsPerKey = (int) countDistribution.get().maxValue(); variableColumnCount = countDistribution.get().minValue() < maxColumnsPerKey; slice = options.slice.setByUser(); } // Option Declarations private static abstract class Options extends GroupedOptions { final OptionSimple superColumns = new OptionSimple("super=", "[0-9]+", "0", "Number of super columns to use (no super columns used if not specified)", false); final OptionSimple comparator = new OptionSimple("comparator=", "TimeUUIDType|AsciiType|UTF8Type", "AsciiType", "Column Comparator to use", false); final OptionSimple slice = new OptionSimple("slice", "", null, "If set, range slices will be used for reads, otherwise a names query will be", false); final OptionSimple timestamp = new OptionSimple("timestamp=", "[0-9]+", null, "If set, all columns will be written with the given timestamp", false); final OptionDistribution size = new OptionDistribution("size=", "FIXED(34)", "Cell size distribution"); } private static final class NameOptions extends Options { final OptionSimple name = new OptionSimple("names=", ".*", null, "Column names", true); @Override public List<? extends Option> options() { return Arrays.asList(name, slice, superColumns, comparator, timestamp, size); } } private static final class CountOptions extends Options { final OptionDistribution count = new OptionDistribution("n=", "FIXED(5)", "Cell count distribution, per operation"); @Override public List<? extends Option> options() { return Arrays.asList(count, slice, superColumns, comparator, timestamp, size); } } // CLI Utility Methods public void printSettings(ResultLogger out) { out.printf(" Max Columns Per Key: %d%n",maxColumnsPerKey); out.printf(" Column Names: %s%n",namestrs); out.printf(" Comparator: %s%n", comparator); out.printf(" Timestamp: %s%n", timestamp); out.printf(" Variable Column Count: %b%n", variableColumnCount); out.printf(" Slice: %b%n", slice); if (sizeDistribution != null){ out.println(" Size Distribution: " + sizeDistribution.getConfigAsString()); }; if (sizeDistribution != null){ out.println(" Count Distribution: " + countDistribution.getConfigAsString()); }; } static SettingsColumn get(Map<String, String[]> clArgs) { String[] params = clArgs.remove("-col"); if (params == null) return new SettingsColumn(new CountOptions()); GroupedOptions options = GroupedOptions.select(params, new NameOptions(), new CountOptions()); if (options == null) { printHelp(); System.out.println("Invalid -col options provided, see output for valid options"); System.exit(1); } return new SettingsColumn(options); } static void printHelp() { GroupedOptions.printOptions(System.out, "-col", new NameOptions(), new CountOptions()); } static Runnable helpPrinter() { return new Runnable() { @Override public void run() { printHelp(); } }; } /* Custom serializaiton invoked here to make legacy thrift based table creation work with StressD. This code requires * the names attribute to be populated. Since the names attribute is set as a List[ByteBuffer] we switch it * to an array on the way out and back to a buffer when it's being read in. */ private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); ArrayList<byte[]> namesBytes = new ArrayList<>(); for (ByteBuffer buffer : this.names) namesBytes.add(ByteBufferUtil.getArray(buffer)); oos.writeObject(namesBytes); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); List<ByteBuffer> namesBuffer = new ArrayList<>(); List<byte[]> namesBytes = (List<byte[]>) ois.readObject(); for (byte[] bytes : namesBytes) namesBuffer.add(ByteBuffer.wrap(bytes)); this.names = new ArrayList<>(namesBuffer); } }