1/*
2 * Copyright (c) 2004, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.internal.agent;
27
28import java.io.IOException;
29import java.nio.ByteBuffer;
30import java.nio.ByteOrder;
31import java.util.HashMap;
32import java.util.Iterator;
33import java.util.List;
34import java.util.Map;
35import java.util.concurrent.atomic.AtomicInteger;
36
37import jdk.internal.perf.Perf;
38import sun.management.counter.Units;
39import sun.management.counter.Counter;
40import sun.management.counter.perf.PerfInstrumentation;
41
42/**
43 * A utility class to support the exporting and importing of the address
44 * of a connector server using the instrumentation buffer.
45 *
46 * @since 1.5
47 */
48public class ConnectorAddressLink {
49    /**
50     * A simple wrapper for the perf-counter backing {@linkplain ByteBuffer}
51     */
52    private static final class PerfHandle {
53        private ByteBuffer bb;
54
55        private PerfHandle(ByteBuffer bb) {
56            this.bb = bb.order(ByteOrder.nativeOrder());
57        }
58
59        private void putLong(long l) {
60            this.bb = bb.clear();
61            this.bb.asLongBuffer().put(l);
62        }
63    }
64
65    private static final String CONNECTOR_ADDRESS_COUNTER =
66            "sun.management.JMXConnectorServer.address";
67    private static final String REMOTE_CONNECTOR_STATE_COUNTER =
68            "sun.management.JMXConnectorServer.remote.enabled";
69
70    /*
71     * The format of the jvmstat counters representing the properties of
72     * a given out-of-the-box JMX remote connector will be as follows:
73     *
74     * sun.management.JMXConnectorServer.<counter>.<key>=<value>
75     *
76     * where:
77     *
78     *     counter = index computed by this class which uniquely identifies
79     *               an out-of-the-box JMX remote connector running in this
80     *               Java virtual machine.
81     *     key/value = a given key/value pair in the map supplied to the
82     *                 exportRemote() method.
83     *
84     * For example,
85     *
86     * sun.management.JMXConnectorServer.0.remoteAddress=service:jmx:rmi:///jndi/rmi://myhost:5000/jmxrmi
87     * sun.management.JMXConnectorServer.0.authenticate=false
88     * sun.management.JMXConnectorServer.0.ssl=false
89     * sun.management.JMXConnectorServer.0.sslRegistry=false
90     * sun.management.JMXConnectorServer.0.sslNeedClientAuth=false
91     */
92    private static final String REMOTE_CONNECTOR_COUNTER_PREFIX =
93            "sun.management.JMXConnectorServer.";
94
95    /*
96     * JMX remote connector counter (it will be incremented every
97     * time a new out-of-the-box JMX remote connector is created).
98     */
99    private static final AtomicInteger counter = new AtomicInteger();
100
101    private static PerfHandle remotePerfHandle = null;
102
103    /**
104     * Exports the specified connector address to the instrumentation buffer
105     * so that it can be read by this or other Java virtual machines running
106     * on the same system.
107     *
108     * @param address The connector address.
109     */
110    public static void export(String address) {
111        if (address == null || address.length() == 0) {
112            throw new IllegalArgumentException("address not specified");
113        }
114        Perf perf = Perf.getPerf();
115        perf.createString(
116            CONNECTOR_ADDRESS_COUNTER, 1, Units.STRING.intValue(), address);
117    }
118
119    public static void unexportRemote() {
120        unexport(remotePerfHandle);
121    }
122
123    private static void unexport(PerfHandle ph) {
124        if (ph != null) {
125            ph.putLong(-1L);
126        }
127    }
128
129    /**
130     * Imports the connector address from the instrument buffer
131     * of the specified Java virtual machine.
132     *
133     * @param vmid an identifier that uniquely identifies a local Java virtual
134     * machine, or <code>0</code> to indicate the current Java virtual machine.
135     *
136     * @return the value of the connector address, or <code>null</code> if the
137     * target VM has not exported a connector address.
138     *
139     * @throws IOException An I/O error occurred while trying to acquire the
140     * instrumentation buffer.
141     */
142    public static String importFrom(int vmid) throws IOException {
143        Perf perf = Perf.getPerf();
144        ByteBuffer bb;
145        try {
146            bb = perf.attach(vmid, "r");
147        } catch (IllegalArgumentException iae) {
148            throw new IOException(iae.getMessage());
149        }
150        List<Counter> counters =
151                new PerfInstrumentation(bb).findByPattern(CONNECTOR_ADDRESS_COUNTER);
152        Iterator<Counter> i = counters.iterator();
153        if (i.hasNext()) {
154            Counter c = i.next();
155            return (String) c.getValue();
156        } else {
157            return null;
158        }
159    }
160
161    /**
162     * Exports the specified remote connector address and associated
163     * configuration properties to the instrumentation buffer so that
164     * it can be read by this or other Java virtual machines running
165     * on the same system.
166     *
167     * @param properties The remote connector address properties.
168     */
169    public static void exportRemote(Map<String, String> properties) {
170        final int index = counter.getAndIncrement();
171        Perf perf = Perf.getPerf();
172        for (Map.Entry<String, String> entry : properties.entrySet()) {
173            perf.createString(REMOTE_CONNECTOR_COUNTER_PREFIX + index + "." +
174                    entry.getKey(), 1, Units.STRING.intValue(), entry.getValue());
175        }
176        if (remotePerfHandle != null) {
177            remotePerfHandle.putLong(index);
178        } else {
179            remotePerfHandle = new PerfHandle(
180                perf.createLong(REMOTE_CONNECTOR_STATE_COUNTER, 1, Units.NONE.intValue(), (long)index)
181            );
182        }
183    }
184
185    /**
186     * Imports the remote connector address and associated
187     * configuration properties from the instrument buffer
188     * of the specified Java virtual machine.
189     *
190     * @param vmid an identifier that uniquely identifies a local Java virtual
191     * machine, or <code>0</code> to indicate the current Java virtual machine.
192     *
193     * @return a map containing the remote connector's properties, or an empty
194     * map if the target VM has not exported the remote connector's properties.
195     *
196     * @throws IOException An I/O error occurred while trying to acquire the
197     * instrumentation buffer.
198     */
199    public static Map<String, String> importRemoteFrom(int vmid) throws IOException {
200        Perf perf = Perf.getPerf();
201        ByteBuffer bb;
202        try {
203            bb = perf.attach(vmid, "r");
204        } catch (IllegalArgumentException iae) {
205            throw new IOException(iae.getMessage());
206        }
207        List<Counter> counters = new PerfInstrumentation(bb).getAllCounters();
208        Map<String, String> properties = new HashMap<>();
209        for (Counter c : counters) {
210            String name =  c.getName();
211            if (name.startsWith(REMOTE_CONNECTOR_COUNTER_PREFIX) &&
212                    !name.equals(CONNECTOR_ADDRESS_COUNTER)) {
213                properties.put(name, c.getValue().toString());
214            }
215        }
216        return properties;
217    }
218}
219