| /* |
| * 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.htrace.core; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.lang.management.ManagementFactory; |
| import java.net.InetAddress; |
| import java.net.NetworkInterface; |
| import java.net.SocketException; |
| import java.util.Enumeration; |
| import java.util.Locale; |
| import java.util.TreeSet; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| /** |
| * <p>The HTrace tracer ID.</p> |
| * |
| * <p>HTrace tracer IDs are created from format strings. |
| * Format strings contain variables which the TracerId class will |
| * replace with the correct values at runtime.</p> |
| * |
| * <ul> |
| * <li>%{tname}: the tracer name supplied when creating the Tracer.</li> |
| * <li>%{pname}: the process name obtained from the JVM.</li> |
| * <li>%{ip}: will be replaced with an ip address.</li> |
| * <li>%{pid}: the numerical process ID from the operating system.</li> |
| * </ul> |
| * |
| * <p>For example, the string "%{pname}/%{ip}" will be replaced with something |
| * like: DataNode/192.168.0.1, assuming that the process' name is DataNode |
| * and its IP address is 192.168.0.1.</p> |
| * |
| * ID strings can contain backslashes as escapes. |
| * For example, "\a" will map to "a". "\%{ip}" will map to the literal |
| * string "%{ip}", not the IP address. A backslash itself can be escaped by a |
| * preceding backslash. |
| */ |
| public final class TracerId { |
| private static final Log LOG = LogFactory.getLog(TracerId.class); |
| |
| /** |
| * The configuration key to use for process id |
| */ |
| public static final String TRACER_ID_KEY = "tracer.id"; |
| |
| /** |
| * The default tracer ID to use if no other ID is configured. |
| */ |
| private static final String DEFAULT_TRACER_ID = "%{tname}/%{ip}"; |
| |
| private final String tracerName; |
| |
| private final String tracerId; |
| |
| public TracerId(HTraceConfiguration conf, String tracerName) { |
| this.tracerName = tracerName; |
| String fmt = conf.get(TRACER_ID_KEY, DEFAULT_TRACER_ID); |
| StringBuilder bld = new StringBuilder(); |
| StringBuilder varBld = null; |
| boolean escaping = false; |
| int varSeen = 0; |
| for (int i = 0, len = fmt.length() ; i < len; i++) { |
| char c = fmt.charAt(i); |
| if (c == '\\') { |
| if (!escaping) { |
| escaping = true; |
| continue; |
| } |
| } |
| switch (varSeen) { |
| case 0: |
| if (c == '%') { |
| if (!escaping) { |
| varSeen = 1; |
| continue; |
| } |
| } |
| escaping = false; |
| varSeen = 0; |
| bld.append(c); |
| break; |
| case 1: |
| if (c == '{') { |
| if (!escaping) { |
| varSeen = 2; |
| varBld = new StringBuilder(); |
| continue; |
| } |
| } |
| escaping = false; |
| varSeen = 0; |
| bld.append("%").append(c); |
| break; |
| default: |
| if (c == '}') { |
| if (!escaping) { |
| String var = varBld.toString(); |
| bld.append(processShellVar(var)); |
| varBld = null; |
| varSeen = 0; |
| continue; |
| } |
| } |
| escaping = false; |
| varBld.append(c); |
| varSeen++; |
| break; |
| } |
| } |
| if (varSeen > 0) { |
| LOG.warn("Unterminated process ID substitution variable at the end " + |
| "of format string " + fmt); |
| } |
| this.tracerId = bld.toString(); |
| if (LOG.isTraceEnabled()) { |
| LOG.trace("ProcessID(fmt=" + fmt + "): computed process ID of \"" + |
| this.tracerId + "\""); |
| } |
| } |
| |
| private String processShellVar(String var) { |
| if (var.equals("tname")) { |
| return tracerName; |
| } else if (var.equals("pname")) { |
| return getProcessName(); |
| } else if (var.equals("ip")) { |
| return getBestIpString(); |
| } else if (var.equals("pid")) { |
| return Long.valueOf(getOsPid()).toString(); |
| } else { |
| LOG.warn("unknown ProcessID variable " + var); |
| return ""; |
| } |
| } |
| |
| static String getProcessName() { |
| String cmdLine = System.getProperty("sun.java.command"); |
| if (cmdLine != null && !cmdLine.isEmpty()) { |
| String fullClassName = cmdLine.split("\\s+")[0]; |
| String[] classParts = fullClassName.split("\\."); |
| cmdLine = classParts[classParts.length - 1]; |
| } |
| return (cmdLine == null || cmdLine.isEmpty()) ? "Unknown" : cmdLine; |
| } |
| |
| /** |
| * <p>Get the best IP address that represents this node.</p> |
| * |
| * This is complicated since nodes can have multiple network interfaces, |
| * and each network interface can have multiple IP addresses. What we're |
| * looking for here is an IP address that will serve to identify this node |
| * to HTrace. So we prefer site-local addresess (i.e. private ones on the |
| * LAN) to publicly routable interfaces. If there are multiple addresses |
| * to choose from, we select the one which comes first in textual sort |
| * order. This should ensure that we at least consistently call each node |
| * by a single name. |
| */ |
| static String getBestIpString() { |
| Enumeration<NetworkInterface> ifaces; |
| try { |
| ifaces = NetworkInterface.getNetworkInterfaces(); |
| } catch (SocketException e) { |
| LOG.error("Error getting network interfaces", e); |
| return "127.0.0.1"; |
| } |
| TreeSet<String> siteLocalCandidates = new TreeSet<String>(); |
| TreeSet<String> candidates = new TreeSet<String>(); |
| while (ifaces.hasMoreElements()) { |
| NetworkInterface iface = ifaces.nextElement(); |
| for (Enumeration<InetAddress> addrs = |
| iface.getInetAddresses(); addrs.hasMoreElements();) { |
| InetAddress addr = addrs.nextElement(); |
| if (!addr.isLoopbackAddress()) { |
| if (addr.isSiteLocalAddress()) { |
| siteLocalCandidates.add(addr.getHostAddress()); |
| } else { |
| candidates.add(addr.getHostAddress()); |
| } |
| } |
| } |
| } |
| if (!siteLocalCandidates.isEmpty()) { |
| return siteLocalCandidates.first(); |
| } |
| if (!candidates.isEmpty()) { |
| return candidates.first(); |
| } |
| return "127.0.0.1"; |
| } |
| |
| /** |
| * <p>Get the process id from the operating system.</p> |
| * |
| * Unfortunately, there is no simple method to get the process id in Java. |
| * The approach we take here is to use the shell method (see |
| * {TracerId#getOsPidFromShellPpid}) unless we are on Windows, where the |
| * shell is not available. On Windows, we use |
| * {TracerId#getOsPidFromManagementFactory}, which depends on some |
| * undocumented features of the JVM, but which doesn't require a shell. |
| */ |
| static long getOsPid() { |
| if ((System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH)). |
| contains("windows")) { |
| return getOsPidFromManagementFactory(); |
| } else { |
| return getOsPidFromShellPpid(); |
| } |
| } |
| |
| /** |
| * <p>Get the process ID by executing a shell and printing the PPID (parent |
| * process ID).</p> |
| * |
| * This method of getting the process ID doesn't depend on any undocumented |
| * features of the virtual machine, and should work on almost any UNIX |
| * operating system. |
| */ |
| private static long getOsPidFromShellPpid() { |
| Process p = null; |
| StringBuilder sb = new StringBuilder(); |
| try { |
| p = new ProcessBuilder("/usr/bin/env", "sh", "-c", "echo $PPID"). |
| redirectErrorStream(true).start(); |
| BufferedReader reader = new BufferedReader( |
| new InputStreamReader(p.getInputStream())); |
| String line = ""; |
| while ((line = reader.readLine()) != null) { |
| sb.append(line.trim()); |
| } |
| int exitVal = p.waitFor(); |
| if (exitVal != 0) { |
| throw new IOException("Process exited with error code " + |
| Integer.valueOf(exitVal).toString()); |
| } |
| } catch (InterruptedException e) { |
| LOG.error("Interrupted while getting operating system pid from " + |
| "the shell.", e); |
| return 0L; |
| } catch (IOException e) { |
| LOG.error("Error getting operating system pid from the shell.", e); |
| return 0L; |
| } finally { |
| if (p != null) { |
| p.destroy(); |
| } |
| } |
| try { |
| return Long.parseLong(sb.toString()); |
| } catch (NumberFormatException e) { |
| LOG.error("Error parsing operating system pid from the shell.", e); |
| return 0L; |
| } |
| } |
| |
| /** |
| * <p>Get the process ID by looking at the name of the managed bean for the |
| * runtime system of the Java virtual machine.</p> |
| * |
| * Although this is undocumented, in the Oracle JVM this name is of the form |
| * [OS_PROCESS_ID]@[HOSTNAME]. |
| */ |
| private static long getOsPidFromManagementFactory() { |
| try { |
| return Long.parseLong(ManagementFactory.getRuntimeMXBean(). |
| getName().split("@")[0]); |
| } catch (NumberFormatException e) { |
| LOG.error("Failed to get the operating system process ID from the name " + |
| "of the managed bean for the JVM.", e); |
| return 0L; |
| } |
| } |
| |
| public String get() { |
| return tracerId; |
| } |
| } |