blob: 64fdc6a76e37df39be3f5aaec666c47ed28b5320 [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.geode.internal.util;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.annotations.internal.MutableForTesting;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.internal.cache.EventID;
/**
* Breadcrumbs establishes traces in thread names that are useful in figuring out what is going on
* in a distributed system given only stack traces.
*
* @since GemFire 20 May 2014
*
*/
public class Breadcrumbs {
private static final ThreadLocal<EventID> EventIDs = new ThreadLocal<EventID>();
@MutableForTesting
public static boolean ENABLED =
Boolean.getBoolean(DistributionConfig.GEMFIRE_PREFIX + "enable-breadcrumbs");
/** delimiter for crumb numbers */
static final String CrumbDelimiter = "/";
/** string that starts a crumb */
static final String CommonBreadcrumbStart = "\n\t" + CrumbDelimiter;
/** all known types of breadcrumbs */
private enum CrumbType {
RECEIVE_SIDE, EVENTID, SEND_SIDE, PROBLEM
}
/** crumb with the highest ordinal, for initialization of delimiter strings */
@Immutable
private static final CrumbType Crumbiest = CrumbType.PROBLEM;
private static final String[] crumbLabels = new String[] {"rcv", "evt", "snd", "oops"};
/** strings that start a particular breadcrumb */
private static final String[] crumbStarts = new String[Crumbiest.ordinal() + 1];
/** strings the terminate a particular breadcrumb */
private static final String[] crumbEnds = new String[Crumbiest.ordinal() + 1];
static {
// initialize breadcrumb delimiter strings
for (int i = 0; i <= Crumbiest.ordinal(); i++) {
crumbStarts[i] = CommonBreadcrumbStart + crumbLabels[i] + CrumbDelimiter + " ";
crumbEnds[i] = " " + CrumbDelimiter + crumbLabels[i] + CrumbDelimiter;
}
}
//////////////////////// P U B L I C A P I
/** set the "sender" breadcrumb for a message while it's being processed */
public static void setReceiveSide(Object crumb) {
if (ENABLED) {
setBreadcrumb(Thread.currentThread(), CrumbType.RECEIVE_SIDE, crumb);
}
}
/** set the "recipients" breadcrumb for a message while waiting for it */
public static void setSendSide(Object crumb) {
if (ENABLED) {
setBreadcrumb(Thread.currentThread(), CrumbType.SEND_SIDE, crumb);
}
}
/** sets the given breadcrumb into the name of the current thread */
public static void setEventId(Object crumb) {
if (ENABLED) {
if (crumb instanceof EventID) {
EventID other = EventIDs.get();
if (other != null && other != crumb) {
((EventID) crumb).incBreadcrumbCounter();
setBreadcrumb(Thread.currentThread(), CrumbType.EVENTID, crumb);
}
} else {
setBreadcrumb(Thread.currentThread(), CrumbType.EVENTID, crumb);
}
}
}
/**
* a problem crumb can be set using I18n message strings and arguments. Breadcrumb will localize
* the string with the given args
*/
public static void setProblem(String msg, Object[] args) {
if (ENABLED) {
setBreadcrumb(Thread.currentThread(), CrumbType.PROBLEM, String.format(msg, args));
}
}
/** sets the given breadcrumb into the name of the current thread */
public static void setProblem(Object crumb) {
if (ENABLED) {
setBreadcrumb(Thread.currentThread(), CrumbType.PROBLEM, crumb);
}
}
/** clears the breadcrumbs from the name of the current thread */
public static void clearBreadcrumb() {
if (ENABLED) {
clearBreadcrumb(Thread.currentThread());
EventIDs.set(null);
}
}
/////////////////////// E N D O F P U B L I C A P I
/** sets the given breadcrumb into the name of the given thread */
private static void setBreadcrumb(Thread t, CrumbType type, Object crumb) {
setCrumbInThread(t, type, crumb);
}
/** clears the breadcrumb from the name of the given thread */
private static void clearBreadcrumb(Thread t) {
String name = t.getName();
int i = name.indexOf(CommonBreadcrumbStart);
if (i >= 0) {
t.setName(name.substring(0, i));
}
}
/**
* This method does all of the work of setting/clearing individual breadcrumbs in thread names
*
* @param t the thread to modify
* @param type the type of breadcrumb
* @param crumb the crumb to insert, or null to clear
*/
private static void setCrumbInThread(Thread t, CrumbType type, Object crumb) {
int typeIndex = type.ordinal();
String name = t.getName();
String crumbString;
if (crumb == null) {
crumbString = ""; // remove existing crumb, if any
} else {
crumbString = crumbStarts[typeIndex] + crumb + crumbEnds[typeIndex];
}
int startIndex = name.indexOf(crumbStarts[typeIndex]);
int endIndex = -1;
if (startIndex < 0) {
// there may be a higher numbered breadcrumb already in the thread name
for (int i = typeIndex + 1; (startIndex < 0) && (i <= Crumbiest.ordinal()); i++) {
startIndex = name.indexOf(crumbStarts[i]);
if (startIndex >= 0) {
endIndex = name.indexOf(crumbEnds[i], startIndex + 1);
}
}
}
if (startIndex < 0) {
// no equal or higher numbered breadcrumbs - just tack this
// one onto the end of the thread's name
t.setName(name + crumbString);
} else if (endIndex > 0) {
// insert before the higher-numbered breadcrumb
t.setName(
name.substring(0, startIndex) + crumbString + name.substring(startIndex, name.length()));
} else {
// replace the existing breadcrumb
endIndex = name.indexOf(crumbEnds[typeIndex], startIndex + 1);
// this shouldn't happen
assert endIndex > 0 : "odd thread name: " + name;
t.setName(name.substring(0, startIndex) + crumbString
+ name.substring(endIndex + crumbEnds[typeIndex].length(), name.length()));
}
}
private static void clearCrumbInThread(Thread t) {
String name = t.getName();
int i = name.indexOf(CommonBreadcrumbStart);
if (i >= 0) {
t.setName(name.substring(0, i));
}
}
public static void main(String args[]) {
String threadName = null;
ENABLED = true;
setReceiveSide("processorId=17332");
threadName = Thread.currentThread().getName();
System.out.println("thread name with sender=" + threadName);
setEventId("eventId");
threadName = Thread.currentThread().getName();
System.out.println("thread name with eventId=" + threadName);
setEventId("eventId2");
threadName = Thread.currentThread().getName();
System.out.println("replaced eventId: " + threadName);
setSendSide("recipients");
threadName = Thread.currentThread().getName();
System.out.println("with recipients: " + threadName);
setEventId("eventId3");
threadName = Thread.currentThread().getName();
System.out.println("replaced eventId3: " + threadName);
setSendSide("recipients2");
threadName = Thread.currentThread().getName();
System.out.println("with recipients2: " + threadName);
setReceiveSide("processorId=22222");
threadName = Thread.currentThread().getName();
System.out.println("thread name with processor 22222=" + threadName);
setSendSide(null);
threadName = Thread.currentThread().getName();
System.out.println("with recipients removed: " + threadName);
clearBreadcrumb();
threadName = Thread.currentThread().getName();
System.out.println("thread name cleared=" + threadName);
}
}