com.netflix.spectator.ipc.ServerGroup Maven / Gradle / Ivy
/*
* Copyright 2014-2019 Netflix, Inc.
*
* Licensed 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 com.netflix.spectator.ipc;
import java.util.Objects;
/**
* Helper for parsing Netflix server group names that follow the Frigga conventions. For
* more information see the IEP documentation for
* server groups.
*
* Frigga is not used for the actual parsing as it is quite inefficient. See the
* ServerGroupParsing benchmark for a comparison.
*/
public class ServerGroup {
/**
* Create a new instance of a server group object by parsing the group name.
*/
public static ServerGroup parse(String asg) {
int d1 = asg.indexOf('-');
int d2 = asg.indexOf('-', d1 + 1);
int dN = asg.lastIndexOf('-');
if (dN < 0 || !isSequence(asg, dN)) {
dN = asg.length();
}
return new ServerGroup(asg, d1, d2, dN);
}
/**
* Check if the last portion of the server group name is a version sequence (v\d+).
*/
private static boolean isSequence(String asg, int dN) {
int length = asg.length();
if (length - dN < 5 || length - dN > 8 || asg.charAt(dN + 1) != 'v') {
// Sequence must be 3 to 6 digits and start with v
// https://github.com/Netflix/frigga/pull/32
return false;
}
for (int i = dN + 2; i < length; ++i) {
if (!Character.isDigit(asg.charAt(i))) {
return false;
}
}
return true;
}
private static String substr(String str, int s, int e) {
return (s >= e) ? null : str.substring(s, e);
}
private final String asg;
private final int d1;
private final int d2;
private final int dN;
/**
* Create a new instance of the server group.
*
* @param asg
* Raw group name received from the user.
* @param d1
* Position of the first dash or -1 if there are no dashes in the input.
* @param d2
* Position of the second dash or -1 if there is not a second dash in the input.
* @param dN
* Position indicating the end of the cluster name. For a server group with a
* sequence this will be the final dash. If the sequence is not present, then
* it will be the end of the string.
*/
ServerGroup(String asg, int d1, int d2, int dN) {
this.asg = asg;
this.d1 = d1;
this.d2 = d2;
this.dN = dN;
}
/** Return the application for the server group or null if invalid. */
public String app() {
if (d1 < 0) {
// No stack or detail is present
return asg.length() > 0 ? asg : null;
} else if (d1 == 0) {
// Application portion is empty
return null;
} else {
// Application is present along with stack, detail, or sequence
return substr(asg, 0, d1);
}
}
/** Return the cluster name for the server group or null if invalid. */
public String cluster() {
if (d1 == 0) {
// Application portion is empty
return null;
} else {
return (dN > 0 && dN == asg.length()) ? asg() : substr(asg, 0, dN);
}
}
/** Return the server group name or null if invalid. */
public String asg() {
return (d1 != 0 && dN > 0) ? asg : null;
}
/** If the server group has a stack, then return the stack name. Otherwise return null. */
public String stack() {
if (d1 <= 0) {
// No stack, detail or sequence is present
return null;
} else if (d2 < 0) {
// Stack, but no detail is present
return substr(asg, d1 + 1, dN);
} else {
// Stack and at least one of detail or sequence is present
return substr(asg, d1 + 1, d2);
}
}
/** If the server group has a detail, then return the detail name. Otherwise return null. */
public String detail() {
return (d1 != 0 && d2 > 0) ? substr(asg, d2 + 1, dN) : null;
}
private boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
private boolean nonZeroDigit(char c) {
return c >= '1' && c <= '9';
}
private String shardN(char n) {
if (d1 != 0 && d2 > 0) {
int matchStart = -1;
int matchEnd = -1;
// Shards must be the first part of the detail, loop until we find a gap
int s = d2;
while (s != -1) {
int nextDash = asg.indexOf('-', s + 2);
int e = (nextDash == -1) ? dN : nextDash;
if (e <= s + 3 || asg.charAt(s + 1) != 'x' || !nonZeroDigit(asg.charAt(s + 2))) {
// Shard value must be at least 1 character
// The number prefix must match
break;
} else if (asg.charAt(s + 2) == n && !isDigit(asg.charAt(s + 3))) {
// If the first character of the value is numeric, means shard number is too high
// skip and move to the next. Otherwise we have a match. Since the match could get
// overwritten later, record positions for now and keep checking.
matchStart = s + 3;
matchEnd = e;
}
s = nextDash;
}
return (matchStart > 0) ? substr(asg, matchStart, matchEnd) : null;
} else {
// No detail means no shards
return null;
}
}
/** If the detail contains a shard with id 1, then return it. Otherwise return null. */
public String shard1() {
return shardN('1');
}
/** If the detail contains a shard with id 2, then return it. Otherwise return null. */
public String shard2() {
return shardN('2');
}
/** If the server group has a sequence number, then return it. Otherwise return null. */
public String sequence() {
return dN == asg.length() ? null : substr(asg, dN + 1, asg.length());
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerGroup that = (ServerGroup) o;
return d1 == that.d1
&& d2 == that.d2
&& dN == that.dN
&& Objects.equals(asg, that.asg);
}
@Override public int hashCode() {
return Objects.hash(asg, d1, d2, dN);
}
@Override public String toString() {
return "ServerGroup(" + asg + ", " + d1 + ", " + d2 + ", " + dN + ")";
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy