-
Notifications
You must be signed in to change notification settings - Fork 126
/
events.go
173 lines (148 loc) · 3.78 KB
/
events.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
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/divan/gotrace/trace"
"io"
"io/ioutil"
"os"
"os/exec"
)
// EventSource defines anything that can
// return tracing events.
type EventSource interface {
Events() ([]*trace.Event, error)
}
// TraceSource implements EventSource for
// pre-made trace file
type TraceSource struct {
// Trace is the path to the trace file.
Trace string
// Binary is a path to binary, needed to symbolize stacks.
// In the future it may be dropped, see
// https://groups.google.com/d/topic/golang-dev/PGX1H8IbhFU
Binary string
}
// NewTraceSource inits new TraceSource.
func NewTraceSource(trace, binary string) *TraceSource {
return &TraceSource{
Trace: trace,
Binary: binary,
}
}
// Events reads trace file from filesystem and symbolizes
// stacks.
func (t *TraceSource) Events() ([]*trace.Event, error) {
f, err := os.Open(t.Trace)
if err != nil {
return nil, err
}
defer f.Close()
return parseTrace(f, t.Binary)
}
func parseTrace(r io.Reader, binary string) ([]*trace.Event, error) {
events, err := trace.Parse(r)
if err != nil {
return nil, err
}
err = trace.Symbolize(events, binary)
return events, err
}
// NativeRun implements EventSource for running app locally,
// using native Go installation.
type NativeRun struct {
OrigPath, Path string
}
// NewNativeRun inits new NativeRun source.
func NewNativeRun(path string) *NativeRun {
return &NativeRun{
OrigPath: path,
}
}
// Events rewrites package if needed, adding Go execution tracer
// to the main function, then builds and runs package using default Go
// installation and returns parsed events.
func (r *NativeRun) Events() ([]*trace.Event, error) {
// rewrite AST
err := r.RewriteSource()
if err != nil {
return nil, fmt.Errorf("couldn't rewrite source code: %v", err)
}
defer func(tmpDir string) {
if err := os.RemoveAll(tmpDir); err != nil {
fmt.Println("Cannot remove temp dir:", err)
}
}(r.Path)
tmpBinary, err := ioutil.TempFile("", "gotracer_build")
if err != nil {
return nil, err
}
defer os.Remove(tmpBinary.Name())
// build binary
// TODO: replace build&run part with "go run" when there is no more need
// to keep binary
cmd := exec.Command("go", "build", "-o", tmpBinary.Name())
var stderr bytes.Buffer
cmd.Stderr = &stderr
cmd.Dir = r.Path
err = cmd.Run()
if err != nil {
fmt.Println("go build error", stderr.String())
// TODO: test on most common errors, possibly add stderr to
// error information or smth.
return nil, err
}
// run
stderr.Reset()
cmd = exec.Command(tmpBinary.Name())
cmd.Stderr = &stderr
if err = cmd.Run(); err != nil {
fmt.Println("modified program failed:", err, stderr.String())
// TODO: test on most common errors, possibly add stderr to
// error information or smth.
return nil, err
}
if stderr.Len() == 0 {
return nil, errors.New("empty trace")
}
// parse trace
return parseTrace(&stderr, tmpBinary.Name())
}
// RewriteSource attempts to add trace-related code if needed.
// TODO: add support for multiple files and package selectors
func (r *NativeRun) RewriteSource() error {
path, err := rewriteSource(r.OrigPath)
if err != nil {
return err
}
r.Path = path
return nil
}
// RawSource implements EventSource for
// raw events in JSON file.
type RawSource struct {
// Path is the path to the JSON file.
Path string
}
// NewRawSource inits new RawSource.
func NewRawSource(path string) *RawSource {
return &RawSource{
Path: path,
}
}
// Events reads JSON file from filesystem and returns events.
func (t *RawSource) Events() ([]*trace.Event, error) {
f, err := os.Open(t.Path)
if err != nil {
return nil, err
}
defer f.Close()
var events []*trace.Event
err = json.NewDecoder(f).Decode(&events)
for _, ev := range events {
fmt.Println("Event", ev)
}
return events, err
}