blob: b4bd7aa5211f0ee6806332b0f7cd556bc9288186 [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.flink.configuration;
import org.apache.flink.annotation.PublicEvolving;
import java.util.Locale;
import static org.apache.flink.configuration.MemorySize.MemoryUnit.hasUnit;
import static org.apache.flink.util.Preconditions.checkArgument;
import static org.apache.flink.util.Preconditions.checkNotNull;
/**
* MemorySize is a representation of a number of bytes, viewable in different units.
*
* <h2>Parsing</h2>
*
* <p>The size can be parsed from a text expression. If the expression is a pure number,
* the value will be interpreted as bytes.
*
* <p>To make larger values more compact, the common size suffixes are supported:
*
* <ul>
* <li>q or 1b or 1bytes (bytes)
* <li>1k or 1kb or 1kibibytes (interpreted as kibibytes = 1024 bytes)
* <li>1m or 1mb or 1mebibytes (interpreted as mebibytes = 1024 kibibytes)
* <li>1g or 1gb or 1gibibytes (interpreted as gibibytes = 1024 mebibytes)
* <li>1t or 1tb or 1tebibytes (interpreted as tebibytes = 1024 gibibytes)
* </ul>
*/
@PublicEvolving
public class MemorySize implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private static final String[] BYTES_UNITS = { "b", "bytes" };
private static final String[] KILO_BYTES_UNITS = { "k", "kb", "kibibytes" };
private static final String[] MEGA_BYTES_UNITS = { "m", "mb", "mebibytes" };
private static final String[] GIGA_BYTES_UNITS = { "g", "gb", "gibibytes" };
private static final String[] TERA_BYTES_UNITS = { "t", "tb", "tebibytes" };
private static final String ALL_UNITS = concatenateUnits(
BYTES_UNITS, KILO_BYTES_UNITS, MEGA_BYTES_UNITS, GIGA_BYTES_UNITS, TERA_BYTES_UNITS);
// ------------------------------------------------------------------------
/** The memory size, in bytes. */
private final long bytes;
/**
* Constructs a new MemorySize.
*
* @param bytes The size, in bytes. Must be zero or larger.
*/
public MemorySize(long bytes) {
checkArgument(bytes >= 0, "bytes must be >= 0");
this.bytes = bytes;
}
// ------------------------------------------------------------------------
/**
* Gets the memory size in bytes.
*/
public long getBytes() {
return bytes;
}
/**
* Gets the memory size in Kibibytes (= 1024 bytes).
*/
public long getKibiBytes() {
return bytes >> 10;
}
/**
* Gets the memory size in Mebibytes (= 1024 Kibibytes).
*/
public long getMebiBytes() {
return bytes >> 20;
}
/**
* Gets the memory size in Gibibytes (= 1024 Mebibytes).
*/
public long getGibiBytes() {
return bytes >> 30;
}
/**
* Gets the memory size in Tebibytes (= 1024 Gibibytes).
*/
public long getTebiBytes() {
return bytes >> 40;
}
// ------------------------------------------------------------------------
@Override
public int hashCode() {
return (int) (bytes ^ (bytes >>> 32));
}
@Override
public boolean equals(Object obj) {
return obj == this ||
(obj != null && obj.getClass() == this.getClass() && ((MemorySize) obj).bytes == this.bytes);
}
@Override
public String toString() {
return bytes + " bytes";
}
// ------------------------------------------------------------------------
// Parsing
// ------------------------------------------------------------------------
/**
* Parses the given string as as MemorySize.
* The supported expressions are listed under {@link MemorySize}.
*
* @param text The string to parse
* @return The parsed MemorySize
*
* @throws IllegalArgumentException Thrown, if the expression cannot be parsed.
*/
public static MemorySize parse(String text) throws IllegalArgumentException {
return new MemorySize(parseBytes(text));
}
/**
* Parses the given string with a default unit.
*
* @param text The string to parse.
* @param defaultUnit specify the default unit.
* @return The parsed MemorySize.
*
* @throws IllegalArgumentException Thrown, if the expression cannot be parsed.
*/
public static MemorySize parse(String text, MemoryUnit defaultUnit) throws IllegalArgumentException {
if (!hasUnit(text)) {
return parse(text + defaultUnit.getUnits()[0]);
}
return parse(text);
}
/**
* Parses the given string as bytes.
* The supported expressions are listed under {@link MemorySize}.
*
* @param text The string to parse
* @return The parsed size, in bytes.
*
* @throws IllegalArgumentException Thrown, if the expression cannot be parsed.
*/
public static long parseBytes(String text) throws IllegalArgumentException {
checkNotNull(text, "text");
final String trimmed = text.trim();
checkArgument(!trimmed.isEmpty(), "argument is an empty- or whitespace-only string");
final int len = trimmed.length();
int pos = 0;
char current;
while (pos < len && (current = trimmed.charAt(pos)) >= '0' && current <= '9') {
pos++;
}
final String number = trimmed.substring(0, pos);
final String unit = trimmed.substring(pos).trim().toLowerCase(Locale.US);
if (number.isEmpty()) {
throw new NumberFormatException("text does not start with a number");
}
final long value;
try {
value = Long.parseLong(number); // this throws a NumberFormatException on overflow
}
catch (NumberFormatException e) {
throw new IllegalArgumentException("The value '" + number +
"' cannot be re represented as 64bit number (numeric overflow).");
}
final long multiplier;
if (unit.isEmpty()) {
multiplier = 1L;
}
else {
if (matchesAny(unit, BYTES_UNITS)) {
multiplier = 1L;
}
else if (matchesAny(unit, KILO_BYTES_UNITS)) {
multiplier = 1024L;
}
else if (matchesAny(unit, MEGA_BYTES_UNITS)) {
multiplier = 1024L * 1024L;
}
else if (matchesAny(unit, GIGA_BYTES_UNITS)) {
multiplier = 1024L * 1024L * 1024L;
}
else if (matchesAny(unit, TERA_BYTES_UNITS)) {
multiplier = 1024L * 1024L * 1024L * 1024L;
}
else {
throw new IllegalArgumentException("Memory size unit '" + unit +
"' does not match any of the recognized units: " + ALL_UNITS);
}
}
final long result = value * multiplier;
// check for overflow
if (result / multiplier != value) {
throw new IllegalArgumentException("The value '" + text +
"' cannot be re represented as 64bit number of bytes (numeric overflow).");
}
return result;
}
private static boolean matchesAny(String str, String[] variants) {
for (String s : variants) {
if (s.equals(str)) {
return true;
}
}
return false;
}
private static String concatenateUnits(final String[]... allUnits) {
final StringBuilder builder = new StringBuilder(128);
for (String[] units : allUnits) {
builder.append('(');
for (String unit : units) {
builder.append(unit);
builder.append(" | ");
}
builder.setLength(builder.length() - 3);
builder.append(") / ");
}
builder.setLength(builder.length() - 3);
return builder.toString();
}
/**
* Enum which defines memory unit, mostly used to parse value from configuration file.
*
* <p>To make larger values more compact, the common size suffixes are supported:
*
* <ul>
* <li>q or 1b or 1bytes (bytes)
* <li>1k or 1kb or 1kibibytes (interpreted as kibibytes = 1024 bytes)
* <li>1m or 1mb or 1mebibytes (interpreted as mebibytes = 1024 kibibytes)
* <li>1g or 1gb or 1gibibytes (interpreted as gibibytes = 1024 mebibytes)
* <li>1t or 1tb or 1tebibytes (interpreted as tebibytes = 1024 gibibytes)
* </ul>
*
*/
public enum MemoryUnit {
BYTES(new String[] { "b", "bytes" }),
KILO_BYTES(new String[] { "k", "kb", "kibibytes" }),
MEGA_BYTES(new String[] { "m", "mb", "mebibytes" }),
GIGA_BYTES(new String[] { "g", "gb", "gibibytes" }),
TERA_BYTES(new String[] { "t", "tb", "tebibytes" });
private String[] units;
MemoryUnit(String[] units) {
this.units = units;
}
public String[] getUnits() {
return units;
}
public static String getAllUnits() {
return concatenateUnits(BYTES.getUnits(), KILO_BYTES.getUnits(), MEGA_BYTES.getUnits(), GIGA_BYTES.getUnits(), TERA_BYTES.getUnits());
}
public static boolean hasUnit(String text) {
checkNotNull(text, "text");
final String trimmed = text.trim();
checkArgument(!trimmed.isEmpty(), "argument is an empty- or whitespace-only string");
final int len = trimmed.length();
int pos = 0;
char current;
while (pos < len && (current = trimmed.charAt(pos)) >= '0' && current <= '9') {
pos++;
}
final String unit = trimmed.substring(pos).trim().toLowerCase(Locale.US);
return unit.length() > 0;
}
private static String concatenateUnits(final String[]... allUnits) {
final StringBuilder builder = new StringBuilder(128);
for (String[] units : allUnits) {
builder.append('(');
for (String unit : units) {
builder.append(unit);
builder.append(" | ");
}
builder.setLength(builder.length() - 3);
builder.append(") / ");
}
builder.setLength(builder.length() - 3);
return builder.toString();
}
}
}