-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathhsperfdata.go
340 lines (303 loc) · 10.6 KB
/
hsperfdata.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
// Package hsperfdata create data: 2018-06-12
// attention: The newest java HotSpot virtual machine performance data structures was V2 when I wrote this code, so these data structures may
// change from release to release, so this parser code only support JVM performance data V2. If there is new version, please open a issue
// or pull request, thx.
package hsperfdata
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
)
// type code http://openjdk.java.net/groups/serviceability/jvmstat/sun/jvmstat/perfdata/monitor/v2_0/TypeCode.html
// source code https://github.com/dmlloyd/openjdk/blob/jdk/jdk/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/v2_0/TypeCode.java
// actually, we use only 'B' and 'J' in HotSpot performance data V2
const (
tBoolean = 'Z'
tChar = 'C'
tFloat = 'F'
tDouble = 'D'
tByte = 'B'
tShort = 'S'
tInt = 'I'
tLong = 'J'
tObject = 'L'
tArray = '['
tVoid = 'V'
)
// variability attribute https://github.com/dmlloyd/openjdk/blob/jdk/jdk/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/monitor/Variability.java
const (
vInvalid = iota
vConstant
vMonotonic
vVariable
)
// unit of measure attribute https://github.com/dmlloyd/openjdk/blob/jdk/jdk/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/monitor/Units.java
const (
uInvalid = iota
uNone
uBytes
uTicks
uEvents
uString
uHertz
)
// perfdataHeader http://openjdk.java.net/groups/serviceability/jvmstat/sun/jvmstat/perfdata/monitor/AbstractPerfDataBufferPrologue.html
// source code https://github.com/dmlloyd/openjdk/blob/jdk/jdk/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/AbstractPerfDataBufferPrologue.java
type perfdataHeader struct {
Magic uint32 // magic number - 0xcafec0c0
ByteOrder byte // big_endian == 0, little_endian == 1
Major byte // major version numbers
Minor byte // minor version numbers
// ReservedByte byte // used as Accessible flag at performance data V2
}
// prologue http://openjdk.java.net/groups/serviceability/jvmstat/sun/jvmstat/perfdata/monitor/v2_0/PerfDataBufferPrologue.html
// source code https://github.com/dmlloyd/openjdk/blob/jdk/jdk/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/v2_0/PerfDataBufferPrologue.java
type bufferPrologueV2 struct {
Accessible byte // Accessible flag at performance data V2
Used int32 // number of PerfData memory bytes used
Overflow int32 // number of bytes of overflow
ModTimestamp int64 // time stamp of the last structural modification
EntryOffset int32 // offset of the first PerfDataEntry
NumEntries int32 // number of allocated PerfData entries
}
// entryHeader http://openjdk.java.net/groups/serviceability/jvmstat/sun/jvmstat/perfdata/monitor/v2_0/PerfDataBuffer.html
// source code https://github.com/dmlloyd/openjdk/blob/jdk/jdk/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/v2_0/PerfDataBuffer.java
type entryHeader struct {
EntryLength int32 // entry length in bytes
NameOffset int32 // offset to entry name, relative to start of entry
VectorLength int32 // length of the vector. If 0, then scalar.
DataType byte // JNI field descriptor type
Flags byte // miscellaneous attribute flags 0x01 - supported
DataUnits byte // unit of measure attribute
DataVar byte // variability attribute
DataOffset int32 // offset to data item, relative to start of entry.
}
// PerfDataPath returns the path to the hsperfdata file for a given pid,
// it searches in all hsperfdata user directories (using a glob mattern),
// pid are assumed to be unique regardless of username.
func PerfDataPath(pid string) (string, error) {
perfGlob := filepath.Join(os.TempDir(), "hsperfdata_*", pid)
perfFiles, err := filepath.Glob(perfGlob)
if err != nil {
return "", err
}
if len(perfFiles) < 1 {
return "", fmt.Errorf("No hsperfdata file found for pid: %s", pid)
}
if len(perfFiles) > 1 {
return "", fmt.Errorf("More than one hsperfdata file found for pid: %s, this is not normal", pid)
}
filePath := perfFiles[0]
return filePath, nil
}
// PerfDataPaths returns a map(pid: dataPath) by the given pids
func PerfDataPaths(pids []string) (map[string]string, error) {
filePaths := make(map[string]string)
for _, pid := range pids {
filePath, err := PerfDataPath(pid)
if err != nil {
return nil, err
}
filePaths[pid] = filePath
}
return filePaths, nil
}
// UserPerfDataPaths returns all the java process hsperfdata path belongs to
// the given user
func UserPerfDataPaths(user string) (map[string]string, error) {
dir := filepath.Join(os.TempDir(), "hsperfdata_"+user)
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
filePaths := make(map[string]string)
for _, f := range files {
if _, err := strconv.Atoi(f.Name()); err == nil {
filePaths[f.Name()] = filepath.Join(dir, f.Name())
}
}
return filePaths, nil
}
// CurrentUserPerfDataPaths returns all the java process hsperfdata path belongs to
// the current user
func CurrentUserPerfDataPaths() (map[string]string, error) {
var user string
if runtime.GOOS == "windows" {
user = os.Getenv("USERNAME")
} else {
user = os.Getenv("USER")
}
if user == "" {
return nil, fmt.Errorf("error: Environment variable USER not set, can not find current user")
}
return UserPerfDataPaths(user)
}
// AllPerfDataPaths returns all users' hsperfdata path
func AllPerfDataPaths() (map[string]string, error) {
dirsGlob := filepath.Join(os.TempDir(), "hsperfdata_*", "*")
paths, err := filepath.Glob(dirsGlob)
if err != nil {
return nil, err
}
filePaths := make(map[string]string)
for _, path := range paths {
pid := filepath.Base(path)
filePaths[pid] = path
}
return filePaths, nil
}
// DataPathsByProcessName get data paths by the given process name
func DataPathsByProcessName(processName string) (map[string]string, error) {
var out []byte
var err error
if runtime.GOOS == "windows" {
out, err = exec.Command("cmd", "/C", "tasklist /NH|findstr /i "+processName).Output()
} else {
out, err = exec.Command("sh", "-c", "ps -ef|grep -i "+processName+"|grep -v grep").Output()
}
if err != nil || len(out) == 0 {
return nil, errors.New(processName + " is not running.")
}
filePaths := make(map[string]string)
buffer := bytes.NewBuffer(out)
for {
line, err := buffer.ReadString('\n')
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
fields := strings.Fields(line)
pid := fields[1]
filePath, err := PerfDataPath(pid)
if err != nil {
continue
}
filePaths[pid] = filePath
}
return filePaths, nil
}
// removeNull remove trailing '\x00' byte of s
func removeNull(s []byte) []byte {
if i := bytes.IndexByte(s, '\x00'); i >= 0 {
return s[:i]
}
return s
}
// ReadPerfData parser hotspot performance data, and return a map represented entries' name and value,
// "ticks" are the unit of measurement of time in the Hotspot JVM.
// when the parserTime is true, tick time value will be parsered to a normal nanoseconds using
// the "sun.os.hrt.frequency" key in the hsperfdata.
func ReadPerfData(filepath string, parserTime bool) (map[string]interface{}, error) {
// read a snapshot into memory
data, err := ioutil.ReadFile(filepath)
if err != nil {
if runtime.GOOS == "windows" {
// on windows, can not read the hsperfdata file when the java process is running, so we copy
// to a new file first, and read the file, then delete it.
_, err = exec.Command("powershell", "-c", "cp", filepath, filepath+"_").Output()
if err != nil {
return nil, err
}
data, err = ioutil.ReadFile(filepath + "_")
if err != nil {
return nil, err
}
_ = os.Remove(filepath + "_")
} else {
return nil, err
}
}
buffer := bytes.NewReader(data)
header := perfdataHeader{}
{
err = binary.Read(buffer, binary.BigEndian, &header)
if err != nil {
return nil, err
}
if header.Magic != 0xcafec0c0 {
return nil, fmt.Errorf("illegal magic %v", header.Magic)
}
// only support 2.0 perf data buffers.
if !(header.Major == 2 && header.Minor == 0) {
return nil, fmt.Errorf("unsupported version %v.%v", header.Major, header.Minor)
}
}
// file endian
var endian binary.ByteOrder
if header.ByteOrder == 0 {
endian = binary.BigEndian
} else {
endian = binary.LittleEndian
}
prologue := bufferPrologueV2{}
{
err = binary.Read(buffer, endian, &prologue)
if prologue.Accessible != 1 {
return nil, fmt.Errorf("not accessible %v", prologue.Accessible)
}
}
entryMap := make(map[string]interface{})
startOffset := prologue.EntryOffset
var frequency time.Duration // the length of a tick
unconvertedTickFields := make(map[string]int64)
for i := int32(0); i < prologue.NumEntries; i++ {
entry := entryHeader{}
buffer.Seek(int64(startOffset), 0)
err = binary.Read(buffer, endian, &entry)
if err != nil {
return nil, fmt.Errorf("Cannot read binary: %v", err)
}
nameStart := int(startOffset) + int(entry.NameOffset)
nameEnd := bytes.Index(data[nameStart:], []byte{'\x00'})
if nameEnd < 0 {
return nil, fmt.Errorf("invalid binary: %v", err)
}
name := string(data[nameStart : int(nameStart)+nameEnd])
dataStart := startOffset + entry.DataOffset
if entry.VectorLength == 0 {
if entry.DataType != tLong {
return nil, fmt.Errorf("Unexpected monitor type: %v", entry.DataType)
}
buffer.Seek(int64(dataStart), 0)
value := int64(0)
err = binary.Read(buffer, endian, &value)
if err != nil {
return nil, err
}
if parserTime && entry.DataUnits == uTicks {
unconvertedTickFields[name] = value
}
if name == "sun.os.hrt.frequency" {
frequency = time.Duration(time.Second.Nanoseconds() / value)
}
entryMap[name] = value
} else {
if entry.DataType != tByte || entry.DataUnits != uString || (entry.DataVar != vConstant && entry.DataVar != vVariable) {
return nil, fmt.Errorf("Unexpected vector monitor: DataType:%c,DataUnits:%v,DataVar:%v", entry.DataType, entry.DataUnits, entry.DataVar)
}
value := string(removeNull(data[dataStart : dataStart+entry.VectorLength]))
entryMap[name] = value
}
startOffset += entry.EntryLength
}
for name, value := range unconvertedTickFields {
value *= frequency.Nanoseconds()
entryMap[name] = value
}
return entryMap, nil
}