/* * 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.hadoop.mapreduce.util; import java.text.ParseException; import java.util.List; import com.google.common.collect.Lists; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.mapreduce.counters.AbstractCounters; import org.apache.hadoop.mapreduce.Counter; import org.apache.hadoop.mapreduce.counters.CounterGroupBase; import org.apache.hadoop.util.StringUtils; /** * String conversion utilities for counters. * Candidate for deprecation since we start to use JSON in 0.21+ */ @InterfaceAudience.Private public class CountersStrings { private static final char GROUP_OPEN = '{'; private static final char GROUP_CLOSE = '}'; private static final char COUNTER_OPEN = '['; private static final char COUNTER_CLOSE = ']'; private static final char UNIT_OPEN = '('; private static final char UNIT_CLOSE = ')'; private static char[] charsToEscape = {GROUP_OPEN, GROUP_CLOSE, COUNTER_OPEN, COUNTER_CLOSE, UNIT_OPEN, UNIT_CLOSE}; /** * Make the pre 0.21 counter string (for e.g. old job history files) * [(actual-name)(display-name)(value)] * @param counter to stringify * @return the stringified result */ public static String toEscapedCompactString(Counter counter) { // First up, obtain the strings that need escaping. This will help us // determine the buffer length apriori. String escapedName, escapedDispName; long currentValue; synchronized(counter) { escapedName = escape(counter.getName()); escapedDispName = escape(counter.getDisplayName()); currentValue = counter.getValue(); } int length = escapedName.length() + escapedDispName.length() + 4; length += 8; // For the following delimiting characters StringBuilder builder = new StringBuilder(length); builder.append(COUNTER_OPEN); // Add the counter name builder.append(UNIT_OPEN); builder.append(escapedName); builder.append(UNIT_CLOSE); // Add the display name builder.append(UNIT_OPEN); builder.append(escapedDispName); builder.append(UNIT_CLOSE); // Add the value builder.append(UNIT_OPEN); builder.append(currentValue); builder.append(UNIT_CLOSE); builder.append(COUNTER_CLOSE); return builder.toString(); } /** * Make the 0.21 counter group string. * format: {(actual-name)(display-name)(value)[][][]} * where [] are compact strings for the counters within. * @param <G> type of the group * @param group to stringify * @return the stringified result */ public static <G extends CounterGroupBase<?>> String toEscapedCompactString(G group) { List<String> escapedStrs = Lists.newArrayList(); int length; String escapedName, escapedDispName; synchronized(group) { // First up, obtain the strings that need escaping. This will help us // determine the buffer length apriori. escapedName = escape(group.getName()); escapedDispName = escape(group.getDisplayName()); int i = 0; length = escapedName.length() + escapedDispName.length(); for (Counter counter : group) { String escapedStr = toEscapedCompactString(counter); escapedStrs.add(escapedStr); length += escapedStr.length(); } } length += 6; // for all the delimiting characters below StringBuilder builder = new StringBuilder(length); builder.append(GROUP_OPEN); // group start // Add the group name builder.append(UNIT_OPEN); builder.append(escapedName); builder.append(UNIT_CLOSE); // Add the display name builder.append(UNIT_OPEN); builder.append(escapedDispName); builder.append(UNIT_CLOSE); // write the value for(String escaped : escapedStrs) { builder.append(escaped); } builder.append(GROUP_CLOSE); // group end return builder.toString(); } /** * Make the pre 0.21 counters string * @param <C> type of the counter * @param <G> type of the counter group * @param <T> type of the counters object * @param counters the object to stringify * @return the string in the following format * {(groupName)(group-displayName)[(counterName)(displayName)(value)]*}* */ public static <C extends Counter, G extends CounterGroupBase<C>, T extends AbstractCounters<C, G>> String toEscapedCompactString(T counters) { String[] groupsArray; int length = 0; synchronized(counters) { groupsArray = new String[counters.countCounters()]; int i = 0; // First up, obtain the escaped string for each group so that we can // determine the buffer length apriori. for (G group : counters) { String escapedString = toEscapedCompactString(group); groupsArray[i++] = escapedString; length += escapedString.length(); } } // Now construct the buffer StringBuilder builder = new StringBuilder(length); for (String group : groupsArray) { builder.append(group); } return builder.toString(); } // Escapes all the delimiters for counters i.e {,[,(,),],} private static String escape(String string) { return StringUtils.escapeString(string, StringUtils.ESCAPE_CHAR, charsToEscape); } // Unescapes all the delimiters for counters i.e {,[,(,),],} private static String unescape(String string) { return StringUtils.unEscapeString(string, StringUtils.ESCAPE_CHAR, charsToEscape); } // Extracts a block (data enclosed within delimeters) ignoring escape // sequences. Throws ParseException if an incomplete block is found else // returns null. private static String getBlock(String str, char open, char close, IntWritable index) throws ParseException { StringBuilder split = new StringBuilder(); int next = StringUtils.findNext(str, open, StringUtils.ESCAPE_CHAR, index.get(), split); split.setLength(0); // clear the buffer if (next >= 0) { ++next; // move over '(' next = StringUtils.findNext(str, close, StringUtils.ESCAPE_CHAR, next, split); if (next >= 0) { ++next; // move over ')' index.set(next); return split.toString(); // found a block } else { throw new ParseException("Unexpected end of block", next); } } return null; // found nothing } /** * Parse a pre 0.21 counters string into a counter object. * @param <C> type of the counter * @param <G> type of the counter group * @param <T> type of the counters object * @param compactString to parse * @param counters an empty counters object to hold the result * @return the counters object holding the result * @throws ParseException */ @SuppressWarnings("deprecation") public static <C extends Counter, G extends CounterGroupBase<C>, T extends AbstractCounters<C, G>> T parseEscapedCompactString(String compactString, T counters) throws ParseException { IntWritable index = new IntWritable(0); // Get the group to work on String groupString = getBlock(compactString, GROUP_OPEN, GROUP_CLOSE, index); while (groupString != null) { IntWritable groupIndex = new IntWritable(0); // Get the actual name String groupName = getBlock(groupString, UNIT_OPEN, UNIT_CLOSE, groupIndex); groupName = unescape(groupName); // Get the display name String groupDisplayName = getBlock(groupString, UNIT_OPEN, UNIT_CLOSE, groupIndex); groupDisplayName = unescape(groupDisplayName); // Get the counters G group = counters.getGroup(groupName); group.setDisplayName(groupDisplayName); String counterString = getBlock(groupString, COUNTER_OPEN, COUNTER_CLOSE, groupIndex); while (counterString != null) { IntWritable counterIndex = new IntWritable(0); // Get the actual name String counterName = getBlock(counterString, UNIT_OPEN, UNIT_CLOSE, counterIndex); counterName = unescape(counterName); // Get the display name String counterDisplayName = getBlock(counterString, UNIT_OPEN, UNIT_CLOSE, counterIndex); counterDisplayName = unescape(counterDisplayName); // Get the value long value = Long.parseLong(getBlock(counterString, UNIT_OPEN, UNIT_CLOSE, counterIndex)); // Add the counter Counter counter = group.findCounter(counterName); counter.setDisplayName(counterDisplayName); counter.increment(value); // Get the next counter counterString = getBlock(groupString, COUNTER_OPEN, COUNTER_CLOSE, groupIndex); } groupString = getBlock(compactString, GROUP_OPEN, GROUP_CLOSE, index); } return counters; } }