org.apache.htrace.core.TracerId.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.htrace.core.TracerId.java

Source

/*
 * 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;
    }
}