Skip to content

Commit 5235c75

Browse files
committed
v2.0 release code
+Bug fixes +Enhanced capabilities
1 parent a3a1734 commit 5235c75

File tree

8 files changed

+335
-82
lines changed

8 files changed

+335
-82
lines changed

README.md

Lines changed: 78 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,82 @@
1-
# yc-data-script
2-
3-
![img](/docs/images/360-degree.png)
4-
5-
## What does the yc-data-script do?
6-
7-
yc-data-script is a simple Golang script that captures 16 different artifacts from your application in a **pristine** manner. These artifacts will be highly useful to troubleshoot performance problems. Below is the list of artifacts captured:
8-
9-
1. Garbage collection log
10-
2. Thread dump
11-
3. Heap dump
12-
4. Heap substitute
13-
5. top
14-
6. ps
15-
7. top -H
16-
8. Disk usage
17-
9. dmesg
18-
10. netstat
19-
11. ping
20-
12. vmstat
21-
13. iostat
22-
14. Kernel parameters
23-
15. Application Log
24-
16. Metadata
25-
26-
## How to run the yc-data-script?
27-
28-
1. Download latest yc-data-script from [this](https://tier1app.com/dist/ycrash/yc-agent-latest.zip) location
29-
2. Unzip the downloaded ```yc-agent-latest.zip``` file. (Say you are unzipping in '/opt/workspace/yc-agent-latest' folder)
30-
3. In the unzipped folder you will find yc-data-script by operating system:
31-
32-
a) ```linux/yc``` - If you are running on Unix/Linux, then use this script.
33-
34-
b) ```windows/yc.exe``` - If you are running on Windows, then use this script.
35-
36-
c) ```mac/yc``` - If you are running on MAC, then use this script.
1+
### Usage of argument options:
2+
```bash
3+
Usage of yc:
4+
-a string
5+
The APP Name of the target
6+
-c string
7+
The config file path to load
8+
-cmd value
9+
The command to be executed, should be paired with '-urlParams' together
10+
-d Delete logs folder created during analyse, default is false
11+
-gcPath string
12+
The gc log file to be uploaded while it exists
13+
-hd
14+
Capture heap dump, default is false
15+
-hdPath string
16+
The heap dump file to be uploaded while it exists
17+
-j string
18+
The java home path to be used. Default will try to use os env 'JAVA_HOME' if 'JAVA_HOME' is not empty, for example: /usr/lib/jvm/java-8-openjdk-amd64
19+
-k string
20+
The API Key that will be used to make API requests, for example: tier1app@12312-12233-1442134-112
21+
-p int
22+
The process Id of the target, for example: 3121
23+
-s string
24+
The server url that will be used to upload data, for example: https://ycrash.companyname.com
25+
-tdPath string
26+
The thread dump file to be uploaded while it exists
27+
-urlParams value
28+
The params to be added at the end of upload request url, should be paired with '-cmd' together
29+
-version
30+
Show the version of this program
3731

3832
```
39-
./yc -j {JAVA_HOME} -onlyCapture -p {PID} -hd
33+
### Example of config file:
34+
```yaml
35+
version: "1"
36+
options:
37+
a: name
38+
d: false
39+
hd: false
40+
j: /usr/lib/jvm/java-8-openjdk-amd64
41+
k: buggycompany@e094aasdsa-c3eb-4c9a-8254-f0dd107245cc
42+
p: 3121
43+
s: https://gceasy.io
44+
gcPath: /var/log/gc.log
45+
hdPath: /var/log/heapdump.log
46+
tdPath: /var/log/threaddump.log
47+
cmds:
48+
- urlParams: dt=vmstat
49+
cmd: vmstat 1 1
4050
```
41-
Where,
42-
43-
**JAVA_HOME** is the home directory where JDK is installed
44-
45-
**PID** is the target JVM's process ID
46-
47-
**Example:**
48-
49-
```
50-
./yc -j /usr/java/jdk1.8.0_141 -onlyCapture -p 15326 -hd
51-
```
52-
53-
When you pass the above arguments, yc-data-script will capture all the application level and system level artifacts/logs from the server from the target JVM & host for analysis. Captured artifacts will be compressed into a zip file and stored in the current directory where the above command was executed. The zip file will have the name in the format: 'yc-YYYY-MM-DDTHH-mm-ss.zip'.
54-
55-
**Example:** 'yc-2021-03-06T14-02-42.zip'.
56-
57-
## How to analyze the artifacts generated by the yc-data-script?
58-
59-
You can analyze the artifacts captured by yc-data-script either manually or through [yCrash server](https://ycrash.io/). yCrash server analyzes all the captured data and generates a root cause analysis report instantly. You can use the [Bundle upload](https://docs.ycrash.io/ycrash-features/bundle-upload.html#step-1-go-to-upload-incident-form) feature in the yCrash server to analyze the captured 360-degree data.
60-
61-
### Advanced launch modes
62-
63-
You can launch yc-data-script in following [3 different modes](https://docs.ycrash.io/ycrash-agent/launch-modes.html#launch-modes):
64-
65-
1. **On-demand Mode:** In this mode you can directly transmit 360-degree artifacts from your server to yCrash server for analysis.
66-
2. **API Mode:** In this mode you can integrate yc-data-script with your current monitoring tools such as AppDynamics, New Relic, Dynatrace, …
67-
3. **M3 (Micro-metrics Monitoring) mode:** In this mode, yc-data-script proactively detect performance outages much earlier before it surfaces
68-
69-
## How to build the yc-data-script?
70-
71-
Please refer to any one of the following links if you want to build the yc-data-script in that corresponding operating system:
7251
73-
1. Build yc-data-script in [Windows](/docs/Build%20yc%20agent%20in%20Windows.pdf)
74-
2. Build yc-data-script in [Linux](/docs/build-yc-agent-linux.md)
75-
3. Build yc-data-script in [MacOS](/docs/build-yc-agent-macos.md)
52+
The config file is using yaml format. The name of the option keys is same as the name of argument options.
53+
54+
'-s': the server url that will be used to upload data.
55+
'-k': the API key that will be used to make API requests.
56+
'-j': the java home path to be used. Default will try to use os env 'JAVA_HOME' if 'JAVA_HOME' is not empty.
57+
'-a': the app name of the target.
58+
'-p': the pid of the target.
59+
'-d': delete logs folder created during analyse, default is false.
60+
'-hd': capture heap dump, default is false.
61+
'-gcPath': the gc log file to be uploaded while it exists, otherwise it will captures one if failed to get the path from '-Xlog:gc' or '-Xloggc'.
62+
'-hdPath': the heap dump file to be uploaded while it exists.
63+
'-tdPath': the thread dump file to be uploaded while it exists, otherwise it will captures one.
64+
65+
Only for argument options:
66+
'-version' show the version of this program.
67+
'-c': the config file path to load.
68+
69+
### Example to capture info from target with pid 3121:
70+
71+
`yc -p 3121 -s https://gceasy.io -k testCompany@e094a34e-c3eb-4c9a-8254-f0dd107245cc -j /usr/lib/jvm/java-11-openjdk-amd64 -c ./config.yaml`
72+
73+
### Example to execute custom commands after the capturing:
74+
75+
- By arguments. One '-urlParams' should be paired with one '-cmd'.
76+
`yc ... -urlParams dt=vmstat -cmd "vmstat 1 1" -urlParams dt=pidstat -cmd "pidstat 1 1" ...`
77+
- By config file.
78+
```yaml
79+
cmds:
80+
- urlParams: dt=vmstat
81+
cmd: vmstat 1 1
82+
```

capture/access_log.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package capture
2+
3+
import (
4+
"io"
5+
"os"
6+
"shell"
7+
"shell/logger"
8+
)
9+
10+
const accessLogOut = "accesslog.out"
11+
12+
type AccessLog struct {
13+
Capture
14+
Path string
15+
Position int64
16+
}
17+
18+
func (t *AccessLog) Run() (result Result, err error) {
19+
if len(t.Path) <= 0 {
20+
return
21+
}
22+
23+
var dst *os.File
24+
var newPosition int64
25+
dst, newPosition, err = prepareLogsForSending(t.Path, t.Position, accessLogOut)
26+
if dst != nil {
27+
defer func() {
28+
_ = dst.Close()
29+
}()
30+
}
31+
32+
if err != nil {
33+
result.Msg = err.Error()
34+
return
35+
}
36+
37+
result.Msg, result.Ok = shell.PostData(t.Endpoint(), "accessLog", dst)
38+
t.Position = newPosition
39+
return
40+
}
41+
42+
func prepareLogsForSending(srcFilePath string, position int64, dstFilePath string) (*os.File, int64, error) {
43+
var dst *os.File
44+
var src *os.File
45+
src, err := os.Open(srcFilePath)
46+
if err != nil {
47+
logger.Log("failed to open accesslog(%s), err: %s", srcFilePath, err.Error())
48+
return nil, position, err
49+
}
50+
defer func() {
51+
err := src.Close()
52+
if err != nil {
53+
logger.Log("failed to close, err: %s", err.Error())
54+
}
55+
}()
56+
dst, err = os.Create(dstFilePath)
57+
if err != nil {
58+
return dst, position, err
59+
}
60+
61+
_, err = src.Seek(position, io.SeekStart)
62+
if err != nil {
63+
logger.Log("failed to seek in source file, err: %s", err.Error())
64+
return dst, position, err
65+
}
66+
copied, err := io.Copy(dst, src)
67+
if err != nil {
68+
logger.Log("unable to copy accessLog, err: %s", err.Error())
69+
return dst, position, err
70+
}
71+
72+
err = dst.Sync()
73+
if err != nil {
74+
logger.Log("failed to sync, err: %s", err.Error())
75+
}
76+
77+
return dst, position + copied, nil
78+
}

capture/access_log_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package capture
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
)
8+
9+
func TestPrepareLogsForSendingFull(t *testing.T) {
10+
srcPath := "access.log"
11+
dstPath := srcPath + ".full"
12+
initPosition := int64(0)
13+
file, pos, err := prepareLogsForSending(srcPath, initPosition, dstPath)
14+
15+
if err != nil {
16+
t.Error(err)
17+
}
18+
fInfo, _ := os.Stat(srcPath)
19+
expectedPos := fInfo.Size()
20+
if pos != expectedPos {
21+
t.Errorf("current position %d is not as expected %d", pos, expectedPos)
22+
}
23+
24+
if file == nil {
25+
t.Errorf("destination file is empty")
26+
}
27+
_ = file.Close()
28+
}
29+
30+
func TestPrepareLogsForSendingRest(t *testing.T) {
31+
srcPath := "access.log"
32+
dstPath := srcPath + ".rest"
33+
initPosition := int64(6)
34+
file, pos, err := prepareLogsForSending(srcPath, initPosition, dstPath)
35+
36+
if err != nil {
37+
t.Error(err)
38+
}
39+
fInfo, _ := os.Stat(srcPath)
40+
expectedPos := fInfo.Size()
41+
if pos != expectedPos {
42+
t.Errorf("current position %d is not as expected %d", pos, expectedPos)
43+
}
44+
45+
if file == nil {
46+
t.Errorf("destination file is empty")
47+
}
48+
}
49+
50+
func TestPrepareLogsForSending(t *testing.T) {
51+
// emulating sequence of operations
52+
initPosition := int64(0)
53+
srcPaths := []string{"access.1.log", "access.2.log", "access.3.log"}
54+
for q, srcPath := range srcPaths {
55+
dstPath := fmt.Sprintf("%s.%d.chunk", srcPath, q+1)
56+
57+
file, pos, err := prepareLogsForSending(srcPath, initPosition, dstPath)
58+
initPosition = pos
59+
if err != nil {
60+
t.Error(err)
61+
}
62+
_ = file.Close()
63+
if file == nil {
64+
t.Errorf("destination file is empty")
65+
}
66+
fInfo, err := os.Stat(srcPath)
67+
if initPosition != fInfo.Size() {
68+
t.Errorf("current position %d is not as expected %d", initPosition, fInfo.Size())
69+
}
70+
}
71+
}

capture/jstack.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func (t *JStack) Run() (result Result, err error) {
8585

8686
// Thread dump: Attempt 5: jstack -F
8787
if jstackFile == nil {
88+
logger.Log("Trying to capture thread dump using jstack -F ...")
8889
jstackFile, err = os.Create(outputFileName)
8990
if err != nil {
9091
logger.Log("Failed to create output file %v", err)
@@ -112,6 +113,38 @@ func (t *JStack) Run() (result Result, err error) {
112113
}
113114
}
114115

116+
// Thread dump: Attempt 6: jhsdb jstack --pid PID
117+
// If you see this error:
118+
// java.lang.RuntimeException: Unable to deduce type of thread from address 0x00007fab10001000 (expected type JavaThread, CompilerThread, ServiceThread, JvmtiAgentThread or CodeCacheSweeperThread)
119+
// It requires the debug information. In ubuntu, you can install it with: apt install openjdk-11-dbg
120+
if jstackFile == nil {
121+
logger.Log("Trying to capture thread dump using jhsdb jstack ...")
122+
123+
jstackFile, err = os.Create(outputFileName)
124+
if err != nil {
125+
logger.Log("Failed to create output file %v", err)
126+
e1 <- err
127+
return
128+
}
129+
130+
_, e := jstackFile.WriteString("\nFull thread dump\n")
131+
if e != nil {
132+
logger.Log("failed to write file %s", e)
133+
e1 <- e
134+
_ = jstackFile.Close()
135+
return
136+
}
137+
138+
err = shell.CommandCombinedOutputToWriter(jstackFile,
139+
shell.Command{path.Join(t.javaHome, "bin/jhsdb"), "jstack", "--pid", strconv.Itoa(t.pid)},
140+
shell.SudoHooker{PID: t.pid},
141+
)
142+
143+
if err != nil {
144+
logger.Log("Failed to run jhsdb jstack with err %v", err)
145+
}
146+
}
147+
115148
var e error
116149
if jstackFile != nil {
117150
e := jstackFile.Sync()

config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ type Options struct {
4343
ProcessTokens ProcessTokens `yaml:"processTokens" usage:"Process tokens of m3 mode"`
4444
ExcludeProcessTokens ProcessTokens `yaml:"excludeTokens" usage:"Process exclude tokens of m3 mode"`
4545

46+
AccessLog string `yaml:"accessLog" usage:"Access log file path which is written by the target application"`
47+
4648
CaptureCmd string `yaml:"captureCmd" usage:"Capture command line to be executed"`
4749

4850
Address string `yaml:"address" usage:"Address to serve API service"`

const.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
// Generic Properties
1313
// ------------------------------------------------------------------------------
1414
var (
15-
SCRIPT_VERSION = "yc_agent_1.0.11"
15+
SCRIPT_VERSION = "yc_agent_2.0"
1616
SCRIPT_SPAN = 120 // How long the whole script should take. Default=240
1717
JAVACORE_INTERVAL = 30 // How often javacores should be taken. Default=30
1818
TOP_INTERVAL = 60 // How often top data should be taken. Default=60

0 commit comments

Comments
 (0)