1/*
2 * Copyright (c) 2004, 2014, 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 sun.jvmstat.perfdata.monitor.v2_0;
27
28import sun.jvmstat.monitor.*;
29import sun.jvmstat.perfdata.monitor.*;
30import java.util.*;
31import java.util.regex.*;
32import java.nio.*;
33
34/**
35 * The concrete implementation of version 2.0 of the HotSpot PerfData
36 * Instrumentation buffer. This class is responsible for parsing the
37 * instrumentation memory and constructing the necessary objects to
38 * represent and access the instrumentation objects contained in the
39 * memory buffer.
40 * <p>
41 * The structure of the 2.0 entry is defined in struct PerfDataEnry
42 * as decsribed in perfMemory.hpp. This structure looks like:
43 * <pre>
44 * typedef struct {
45 *   jint entry_length;         // entry length in bytes
46 *   jint name_offset;          // offset to entry name, relative to start
47 *                              // of entry
48 *   jint vector_length;        // length of the vector. If 0, then scalar.
49 *   jbyte data_type;           // JNI field descriptor type
50 *   jbyte flags;               // miscellaneous attribute flags
51 *                              // 0x01 - supported
52 *   jbyte data_units;          // unit of measure attribute
53 *   jbyte data_variability;    // variability attribute
54 *   jbyte data_offset;         // offset to data item, relative to start
55 *                              // of entry.
56 * } PerfDataEntry;
57 * </pre>
58 *
59 * @author Brian Doherty
60 * @since 1.5
61 * @see AbstractPerfDataBuffer
62 */
63public class PerfDataBuffer extends PerfDataBufferImpl {
64
65    // 8028357 removed old, inefficient debug logging
66
67    private static final int syncWaitMs =
68            Integer.getInteger("sun.jvmstat.perdata.syncWaitMs", 5000);
69    private static final ArrayList<Monitor> EMPTY_LIST = new ArrayList<>(0);
70
71    /*
72     * These are primarily for documentary purposes and the match up
73     * with the PerfDataEntry structure in perfMemory.hpp. They are
74     * generally unused in this code, but they are kept consistent with
75     * the data structure just in case some unforseen need arrises.
76     */
77    private final static int PERFDATA_ENTRYLENGTH_OFFSET=0;
78    private final static int PERFDATA_ENTRYLENGTH_SIZE=4;   // sizeof(int)
79    private final static int PERFDATA_NAMEOFFSET_OFFSET=4;
80    private final static int PERFDATA_NAMEOFFSET_SIZE=4;    // sizeof(int)
81    private final static int PERFDATA_VECTORLENGTH_OFFSET=8;
82    private final static int PERFDATA_VECTORLENGTH_SIZE=4;  // sizeof(int)
83    private final static int PERFDATA_DATATYPE_OFFSET=12;
84    private final static int PERFDATA_DATATYPE_SIZE=1;      // sizeof(byte)
85    private final static int PERFDATA_FLAGS_OFFSET=13;
86    private final static int PERFDATA_FLAGS_SIZE=1;       // sizeof(byte)
87    private final static int PERFDATA_DATAUNITS_OFFSET=14;
88    private final static int PERFDATA_DATAUNITS_SIZE=1;     // sizeof(byte)
89    private final static int PERFDATA_DATAVAR_OFFSET=15;
90    private final static int PERFDATA_DATAVAR_SIZE=1;       // sizeof(byte)
91    private final static int PERFDATA_DATAOFFSET_OFFSET=16;
92    private final static int PERFDATA_DATAOFFSET_SIZE=4;    // sizeof(int)
93
94    PerfDataBufferPrologue prologue;
95    int nextEntry;
96    long lastNumEntries;
97    IntegerMonitor overflow;
98    ArrayList<Monitor> insertedMonitors;
99
100    /**
101     * Construct a PerfDataBuffer instance.
102     * <p>
103     * This class is dynamically loaded by
104     * {@link AbstractPerfDataBuffer#createPerfDataBuffer}, and this
105     * constructor is called to instantiate the instance.
106     *
107     * @param buffer the buffer containing the instrumentation data
108     * @param lvmid the Local Java Virtual Machine Identifier for this
109     *              instrumentation buffer.
110     */
111    public PerfDataBuffer(ByteBuffer buffer, int lvmid)
112           throws MonitorException {
113        super(buffer, lvmid);
114        prologue = new PerfDataBufferPrologue(buffer);
115        this.buffer.order(prologue.getByteOrder());
116    }
117
118    /**
119     * {@inheritDoc}
120     */
121    protected void buildMonitorMap(Map<String, Monitor>  map) throws MonitorException {
122        assert Thread.holdsLock(this);
123
124        // start at the beginning of the buffer
125        buffer.rewind();
126
127        // create pseudo monitors
128        buildPseudoMonitors(map);
129
130        // wait for the target JVM to indicate that it's intrumentation
131        // buffer is safely accessible
132        synchWithTarget();
133
134        // parse the currently defined entries starting at the first entry.
135        nextEntry = prologue.getEntryOffset();
136
137        // record the number of entries before parsing the structure
138        int numEntries = prologue.getNumEntries();
139
140        // start parsing
141        Monitor monitor = getNextMonitorEntry();
142        while (monitor != null) {
143            map.put(monitor.getName(), monitor);
144            monitor = getNextMonitorEntry();
145        }
146
147        /*
148         * keep track of the current number of entries in the shared
149         * memory for new entry detection purposes. It's possible for
150         * the data structure to be modified while the Map is being
151         * built and the entry count in the header might change while
152         * we are parsing it. The map will contain all the counters
153         * found, but the number recorded in numEntries might be small
154         * than what than the number we actually parsed (due to asynchronous
155         * updates). This discrepency is handled by ignoring any re-parsed
156         * entries when updating the Map in getNewMonitors().
157         */
158        lastNumEntries = numEntries;
159
160        // keep track of the monitors just added.
161        insertedMonitors = new ArrayList<Monitor>(map.values());
162    }
163
164    /**
165     * {@inheritDoc}
166     */
167    protected void getNewMonitors(Map<String, Monitor> map) throws MonitorException {
168        assert Thread.holdsLock(this);
169
170        int numEntries = prologue.getNumEntries();
171
172        if (numEntries > lastNumEntries) {
173            lastNumEntries = numEntries;
174            Monitor monitor = getNextMonitorEntry();
175
176            while (monitor != null) {
177                String name = monitor.getName();
178
179                // guard against re-parsed entries
180                if (!map.containsKey(name)) {
181                    map.put(name, monitor);
182                    if (insertedMonitors != null) {
183                        insertedMonitors.add(monitor);
184                    }
185                }
186                monitor = getNextMonitorEntry();
187            }
188        }
189    }
190
191    /**
192     * {@inheritDoc}
193     */
194    protected MonitorStatus getMonitorStatus(Map<String, Monitor> map) throws MonitorException {
195        assert Thread.holdsLock(this);
196        assert insertedMonitors != null;
197
198        // load any new monitors
199        getNewMonitors(map);
200
201        // current implementation doesn't support deletion of reuse of entries
202        ArrayList<Monitor> removed = EMPTY_LIST;
203        ArrayList<Monitor> inserted = insertedMonitors;
204
205        insertedMonitors = new ArrayList<>();
206        return new MonitorStatus(inserted, removed);
207    }
208
209    /**
210     * Build the pseudo monitors used to map the prolog data into counters.
211     */
212    protected void buildPseudoMonitors(Map<String, Monitor> map) {
213        Monitor monitor = null;
214        String name = null;
215        IntBuffer ib = null;
216
217        name = PerfDataBufferPrologue.PERFDATA_MAJOR_NAME;
218        ib = prologue.majorVersionBuffer();
219        monitor = new PerfIntegerMonitor(name, Units.NONE,
220                                         Variability.CONSTANT, false, ib);
221        map.put(name, monitor);
222
223        name = PerfDataBufferPrologue.PERFDATA_MINOR_NAME;
224        ib = prologue.minorVersionBuffer();
225        monitor = new PerfIntegerMonitor(name, Units.NONE,
226                                         Variability.CONSTANT, false, ib);
227        map.put(name, monitor);
228
229        name = PerfDataBufferPrologue.PERFDATA_BUFFER_SIZE_NAME;
230        ib = prologue.sizeBuffer();
231        monitor = new PerfIntegerMonitor(name, Units.BYTES,
232                                         Variability.MONOTONIC, false, ib);
233        map.put(name, monitor);
234
235        name = PerfDataBufferPrologue.PERFDATA_BUFFER_USED_NAME;
236        ib = prologue.usedBuffer();
237        monitor = new PerfIntegerMonitor(name, Units.BYTES,
238                                         Variability.MONOTONIC, false, ib);
239        map.put(name, monitor);
240
241        name = PerfDataBufferPrologue.PERFDATA_OVERFLOW_NAME;
242        ib = prologue.overflowBuffer();
243        monitor = new PerfIntegerMonitor(name, Units.BYTES,
244                                         Variability.MONOTONIC, false, ib);
245        map.put(name, monitor);
246        this.overflow = (IntegerMonitor)monitor;
247
248        name = PerfDataBufferPrologue.PERFDATA_MODTIMESTAMP_NAME;
249        LongBuffer lb = prologue.modificationTimeStampBuffer();
250        monitor = new PerfLongMonitor(name, Units.TICKS,
251                                      Variability.MONOTONIC, false, lb);
252        map.put(name, monitor);
253    }
254
255    /**
256     * Method that waits until the target jvm indicates that
257     * its shared memory is safe to access.
258     */
259    protected void synchWithTarget() throws MonitorException {
260        /*
261         * synch must happen with syncWaitMs from now. Default is 5 seconds,
262         * which is reasonabally generous and should provide for extreme
263         * situations like startup delays due to allocation of large ISM heaps.
264         */
265        long timeLimit = System.currentTimeMillis() + syncWaitMs;
266
267        // loop waiting for the accessible indicater to be non-zero
268        while (!prologue.isAccessible()) {
269
270            // give the target jvm a chance to complete initializatoin
271            try { Thread.sleep(20); } catch (InterruptedException e) { }
272
273            if (System.currentTimeMillis() > timeLimit) {
274                throw new MonitorException("Could not synchronize with target");
275            }
276        }
277    }
278
279    /**
280     * method to extract the next monitor entry from the instrumentation memory.
281     * assumes that nextEntry is the offset into the byte array
282     * at which to start the search for the next entry. method leaves
283     * next entry pointing to the next entry or to the end of data.
284     */
285    protected Monitor getNextMonitorEntry() throws MonitorException {
286        Monitor monitor = null;
287
288        // entries are always 4 byte aligned.
289        if ((nextEntry % 4) != 0) {
290            throw new MonitorStructureException(
291                    "Misaligned entry index: "
292                    + Integer.toHexString(nextEntry));
293        }
294
295        // protect againt a corrupted shard memory region.
296        if ((nextEntry < 0)  || (nextEntry > buffer.limit())) {
297            throw new MonitorStructureException(
298                    "Entry index out of bounds: "
299                    + Integer.toHexString(nextEntry)
300                    + ", limit = " + Integer.toHexString(buffer.limit()));
301        }
302
303        // check for end of the buffer
304        if (nextEntry == buffer.limit()) {
305            return null;
306        }
307
308        buffer.position(nextEntry);
309
310        int entryStart = buffer.position();
311        int entryLength = buffer.getInt();
312
313        // check for valid entry length
314        if ((entryLength < 0) || (entryLength > buffer.limit())) {
315            throw new MonitorStructureException(
316                    "Invalid entry length: entryLength = " + entryLength
317                    + " (0x" + Integer.toHexString(entryLength) + ")");
318        }
319
320        // check if last entry occurs before the eof.
321        if ((entryStart + entryLength) > buffer.limit()) {
322            throw new MonitorStructureException(
323                    "Entry extends beyond end of buffer: "
324                    + " entryStart = 0x" + Integer.toHexString(entryStart)
325                    + " entryLength = 0x" + Integer.toHexString(entryLength)
326                    + " buffer limit = 0x" + Integer.toHexString(buffer.limit()));
327        }
328
329        if (entryLength == 0) {
330            // end of data
331            return null;
332        }
333
334        // we can safely read this entry
335        int nameOffset = buffer.getInt();
336        int vectorLength = buffer.getInt();
337        byte typeCodeByte = buffer.get();
338        byte flags = buffer.get();
339        byte unitsByte = buffer.get();
340        byte varByte = buffer.get();
341        int dataOffset = buffer.getInt();
342
343        // convert common attributes to their object types
344        Units units = Units.toUnits(unitsByte);
345        Variability variability = Variability.toVariability(varByte);
346        TypeCode typeCode = null;
347        boolean supported = (flags & 0x01) != 0;
348
349        try {
350            typeCode = TypeCode.toTypeCode(typeCodeByte);
351
352        } catch (IllegalArgumentException e) {
353            throw new MonitorStructureException(
354                    "Illegal type code encountered:"
355                    + " entry_offset = 0x" + Integer.toHexString(nextEntry)
356                    + ", type_code = " + Integer.toHexString(typeCodeByte));
357        }
358
359        // verify that the name_offset is contained within the entry bounds
360        if (nameOffset > entryLength) {
361            throw new MonitorStructureException(
362                    "Field extends beyond entry bounds"
363                    + " entry_offset = 0x" + Integer.toHexString(nextEntry)
364                    + ", name_offset = 0x" + Integer.toHexString(nameOffset));
365        }
366
367        // verify that the data_offset is contained within the entry bounds
368        if (dataOffset > entryLength) {
369            throw new MonitorStructureException(
370                    "Field extends beyond entry bounds:"
371                    + " entry_offset = 0x" + Integer.toHexString(nextEntry)
372                    + ", data_offset = 0x" + Integer.toHexString(dataOffset));
373        }
374
375        // validate the variability and units fields
376        if (variability == Variability.INVALID) {
377            throw new MonitorDataException(
378                    "Invalid variability attribute:"
379                    + " entry_offset = 0x" + Integer.toHexString(nextEntry)
380                    + ", variability = 0x" + Integer.toHexString(varByte));
381        }
382
383        if (units == Units.INVALID) {
384            throw new MonitorDataException(
385                    "Invalid units attribute: entry_offset = 0x"
386                    + Integer.toHexString(nextEntry)
387                    + ", units = 0x" + Integer.toHexString(unitsByte));
388        }
389
390        // the entry looks good - parse the variable length components
391
392        /*
393         * The name starts at nameOffset and continues up to the first null
394         * byte. however, we don't know the length, but we can approximate it
395         * without searching for the null by using the offset for the data
396         * field, which follows the name field.
397         */
398        assert (buffer.position() == (entryStart + nameOffset));
399        assert (dataOffset > nameOffset);
400
401        // include possible pad space
402        int maxNameLength = dataOffset-nameOffset;
403
404        // maxNameLength better be less than the total entry length
405        assert (maxNameLength < entryLength);
406
407        // collect the characters, but do not collect the null byte,
408        // as the String(byte[]) constructor does not ignore it!
409        byte[] nameBytes = new byte[maxNameLength];
410        int nameLength = 0;
411        byte b;
412        while (((b = buffer.get()) != 0) && (nameLength < maxNameLength)) {
413             nameBytes[nameLength++] = b;
414        }
415
416        assert (nameLength < maxNameLength);
417
418        // we should before or at the start of the data field
419        assert (buffer.position() <= (entryStart + dataOffset));
420
421        // convert the name bytes into a String
422        String name = new String(nameBytes, 0, nameLength);
423
424        /*
425         * compute the size of the data item - this includes pad
426         * characters used to align the next entry.
427         */
428        int dataSize = entryLength - dataOffset;
429
430        // set the position to the start of the data item
431        buffer.position(entryStart + dataOffset);
432
433        if (vectorLength == 0) {
434            // create a scalar Monitor object
435            if (typeCode == TypeCode.LONG) {
436                LongBuffer lb = buffer.asLongBuffer();
437                lb.limit(1);  // limit buffer size to one long value.
438                monitor = new PerfLongMonitor(name, units, variability,
439                                              supported, lb);
440            } else {
441                /*
442                 * unexpected type code - coding error or uncoordinated
443                 * JVM change
444                 */
445                throw new MonitorTypeException(
446                        "Unexpected type code encountered:"
447                        + " entry_offset = 0x" + Integer.toHexString(nextEntry)
448                        + ", name = " + name
449                        + ", type_code = " + typeCode
450                        + " (0x" + Integer.toHexString(typeCodeByte) + ")");
451            }
452        } else {
453            // create a vector Monitor object
454            if (typeCode == TypeCode.BYTE) {
455                if (units != Units.STRING) {
456                    // only byte arrays of type STRING are currently supported
457                    throw new MonitorTypeException(
458                            "Unexpected vector type encounterd:"
459                            + " entry_offset = "
460                            + Integer.toHexString(nextEntry)
461                            + ", name = " + name
462                            + ", type_code = " + typeCode + " (0x"
463                            + Integer.toHexString(typeCodeByte) + ")"
464                            + ", units = " + units + " (0x"
465                            + Integer.toHexString(unitsByte) + ")");
466                }
467
468                ByteBuffer bb = buffer.slice();
469                bb.limit(vectorLength); // limit buffer length to # of chars
470
471                if (variability == Variability.CONSTANT) {
472                    monitor = new PerfStringConstantMonitor(name, supported,
473                                                            bb);
474                } else if (variability == Variability.VARIABLE) {
475                    monitor = new PerfStringVariableMonitor(name, supported,
476                                                            bb, vectorLength-1);
477                } else if (variability == Variability.MONOTONIC) {
478                    // Monotonically increasing byte arrays are not supported
479                    throw new MonitorDataException(
480                            "Unexpected variability attribute:"
481                            + " entry_offset = 0x"
482                            + Integer.toHexString(nextEntry)
483                            + " name = " + name
484                            + ", variability = " + variability + " (0x"
485                            + Integer.toHexString(varByte) + ")");
486                } else {
487                    // variability was validated above, so this unexpected
488                    assert false;
489                }
490            } else {
491                // coding error or uncoordinated JVM change
492                throw new MonitorTypeException(
493                        "Unexpected type code encountered:"
494                        + " entry_offset = 0x"
495                        + Integer.toHexString(nextEntry)
496                        + ", name = " + name
497                        + ", type_code = " + typeCode + " (0x"
498                        + Integer.toHexString(typeCodeByte) + ")");
499            }
500        }
501
502        // setup index to next entry for next iteration of the loop.
503        nextEntry = entryStart + entryLength;
504        return monitor;
505    }
506}
507