1
2
3
4
5 package upload
6
7 import (
8 "crypto/rand"
9 "encoding/binary"
10 "encoding/json"
11 "fmt"
12 "math"
13 "os"
14 "path/filepath"
15 "strings"
16 "time"
17
18 "golang.org/x/telemetry/internal/config"
19 "golang.org/x/telemetry/internal/counter"
20 "golang.org/x/telemetry/internal/telemetry"
21 )
22
23
24 func (u *uploader) reports(todo *work) ([]string, error) {
25 if mode, _ := u.dir.Mode(); mode == "off" {
26 return nil, nil
27 }
28 thisInstant := u.startTime
29 today := thisInstant.Format(time.DateOnly)
30 lastWeek := latestReport(todo.uploaded)
31 if lastWeek >= today {
32 lastWeek = ""
33 }
34 u.logger.Printf("Last week: %s, today: %s", lastWeek, today)
35 countFiles := make(map[string][]string)
36 earliest := make(map[string]time.Time)
37 for _, f := range todo.countfiles {
38 begin, end, err := u.counterDateSpan(f)
39 if err != nil {
40
41
42 u.logger.Printf("BUG: failed to parse expiry for collected count file: %v", err)
43 continue
44 }
45
46 if end.Before(thisInstant) {
47 expiry := end.Format(dateFormat)
48 countFiles[expiry] = append(countFiles[expiry], f)
49 if earliest[expiry].IsZero() || earliest[expiry].After(begin) {
50 earliest[expiry] = begin
51 }
52 }
53 }
54 for expiry, files := range countFiles {
55 if notNeeded(expiry, *todo) {
56 u.logger.Printf("Files for %s not needed, deleting %v", expiry, files)
57
58
59 u.deleteFiles(files)
60 continue
61 }
62 fname, err := u.createReport(earliest[expiry], expiry, files, lastWeek)
63 if err != nil {
64 u.logger.Printf("Failed to create report for %s: %v", expiry, err)
65 continue
66 }
67 if fname != "" {
68 u.logger.Printf("Ready to upload: %s", filepath.Base(fname))
69 todo.readyfiles = append(todo.readyfiles, fname)
70 }
71 }
72 return todo.readyfiles, nil
73 }
74
75
76
77 func latestReport(uploaded map[string]bool) string {
78 var latest string
79 for name := range uploaded {
80 if strings.HasSuffix(name, ".json") {
81 if name > latest {
82 latest = name
83 }
84 }
85 }
86 if latest == "" {
87 return ""
88 }
89
90 return latest[:len(latest)-len(".json")]
91 }
92
93
94 func notNeeded(date string, todo work) bool {
95 if todo.uploaded != nil && todo.uploaded[date+".json"] {
96 return true
97 }
98
99 for _, f := range todo.readyfiles {
100 if strings.Contains(f, date) {
101 return true
102 }
103 }
104 return false
105 }
106
107 func (u *uploader) deleteFiles(files []string) {
108 for _, f := range files {
109 if err := os.Remove(f); err != nil {
110
111
112
113 u.logger.Printf("%v failed to remove %s", err, f)
114 }
115 }
116 }
117
118
119
120
121
122
123 func (u *uploader) createReport(start time.Time, expiryDate string, countFiles []string, lastWeek string) (string, error) {
124 uploadOK := true
125 mode, asof := u.dir.Mode()
126 if mode != "on" {
127 u.logger.Printf("No upload config or mode %q is not 'on'", mode)
128 uploadOK = false
129 }
130 if u.tooOld(expiryDate, u.startTime) {
131 u.logger.Printf("Expiry date %s is too old", expiryDate)
132 uploadOK = false
133 }
134
135
136 if !asof.IsZero() && !asof.Before(start) {
137 u.logger.Printf("As-of date %s is not before start %s", asof, start)
138 uploadOK = false
139 }
140
141 report := &telemetry.Report{
142 Config: u.configVersion,
143 X: computeRandom(),
144 Week: expiryDate,
145 LastWeek: lastWeek,
146 }
147 if report.X > u.config.SampleRate && u.config.SampleRate > 0 {
148 u.logger.Printf("X: %f > SampleRate:%f, not uploadable", report.X, u.config.SampleRate)
149 uploadOK = false
150 }
151 var succeeded bool
152 for _, f := range countFiles {
153 fok := false
154 x, err := u.parseCountFile(f)
155 if err != nil {
156 u.logger.Printf("Unparseable count file %s: %v", filepath.Base(f), err)
157 continue
158 }
159 prog := findProgReport(x.Meta, report)
160 for k, v := range x.Count {
161 if counter.IsStackCounter(k) {
162
163 prog.Stacks[k] += int64(v)
164 } else {
165
166 prog.Counters[k] += int64(v)
167 }
168 succeeded = true
169 fok = true
170 }
171 if !fok {
172 u.logger.Printf("no counters found in %s", f)
173 }
174 }
175 if !succeeded {
176 return "", fmt.Errorf("none of the %d count files for %s contained counters", len(countFiles), expiryDate)
177 }
178
179 localContents, err := json.MarshalIndent(report, "", " ")
180 if err != nil {
181 return "", fmt.Errorf("failed to marshal report for %s: %v", expiryDate, err)
182 }
183
184
185 var report2 telemetry.Report
186 if err := json.Unmarshal(localContents, &report2); err != nil {
187 return "", fmt.Errorf("failed to unmarshal local report for %s: %v", expiryDate, err)
188 }
189
190 var uploadContents []byte
191 if uploadOK {
192
193 cfg := config.NewConfig(u.config)
194 upload := &telemetry.Report{
195 Week: report.Week,
196 LastWeek: report.LastWeek,
197 X: report.X,
198 Config: report.Config,
199 }
200 for _, p := range report.Programs {
201
202
203
204 if !cfg.HasGoVersion(p.GoVersion) || !cfg.HasProgram(p.Program) || !cfg.HasVersion(p.Program, p.Version) {
205 continue
206 }
207 x := &telemetry.ProgramReport{
208 Program: p.Program,
209 Version: p.Version,
210 GOOS: p.GOOS,
211 GOARCH: p.GOARCH,
212 GoVersion: p.GoVersion,
213 Counters: make(map[string]int64),
214 Stacks: make(map[string]int64),
215 }
216 upload.Programs = append(upload.Programs, x)
217 for k, v := range p.Counters {
218 if cfg.HasCounter(p.Program, k) && report.X <= cfg.Rate(p.Program, k) {
219 x.Counters[k] = v
220 }
221 }
222
223
224 for k, v := range p.Stacks {
225 before, _, _ := strings.Cut(k, "\n")
226 if cfg.HasStack(p.Program, before) && report.X <= cfg.Rate(p.Program, before) {
227 x.Stacks[k] = v
228 }
229 }
230 }
231
232 uploadContents, err = json.MarshalIndent(upload, "", " ")
233 if err != nil {
234 return "", fmt.Errorf("failed to marshal upload report for %s: %v", expiryDate, err)
235 }
236 }
237 localFileName := filepath.Join(u.dir.LocalDir(), "local."+expiryDate+".json")
238 uploadFileName := filepath.Join(u.dir.LocalDir(), expiryDate+".json")
239
240
241
242
243 if _, err := os.Stat(localFileName); err == nil {
244 u.deleteFiles(countFiles)
245 return "", fmt.Errorf("local report %s already exists", localFileName)
246 }
247 if _, err := os.Stat(uploadFileName); err == nil {
248 u.deleteFiles(countFiles)
249 return "", fmt.Errorf("report %s already exists", uploadFileName)
250 }
251
252 var errUpload, errLocal error
253 if uploadOK {
254 _, errUpload = exclusiveWrite(uploadFileName, uploadContents)
255 }
256
257 _, errLocal = exclusiveWrite(localFileName, localContents)
258
259
260
261
262 if errLocal != nil {
263 return "", fmt.Errorf("failed to write local file %s (%v)", localFileName, errLocal)
264 }
265 if errUpload != nil {
266 return "", fmt.Errorf("failed to write upload file %s (%v)", uploadFileName, errUpload)
267 }
268 u.logger.Printf("Created %s, deleting %d count files", filepath.Base(uploadFileName), len(countFiles))
269 u.deleteFiles(countFiles)
270 if uploadOK {
271 return uploadFileName, nil
272 }
273 return "", nil
274 }
275
276
277
278
279
280
281
282 func exclusiveWrite(filename string, content []byte) (_ bool, rerr error) {
283 f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
284 if err != nil {
285 if os.IsExist(err) {
286 return false, nil
287 }
288 return false, err
289 }
290 defer func() {
291 if err := f.Close(); err != nil && rerr == nil {
292 rerr = err
293 }
294 }()
295 if _, err := f.Write(content); err != nil {
296 return false, err
297 }
298 return true, nil
299 }
300
301
302 func findProgReport(meta map[string]string, report *telemetry.Report) *telemetry.ProgramReport {
303 for _, prog := range report.Programs {
304 if prog.Program == meta["Program"] && prog.Version == meta["Version"] &&
305 prog.GoVersion == meta["GoVersion"] && prog.GOOS == meta["GOOS"] &&
306 prog.GOARCH == meta["GOARCH"] {
307 return prog
308 }
309 }
310 prog := telemetry.ProgramReport{
311 Program: meta["Program"],
312 Version: meta["Version"],
313 GoVersion: meta["GoVersion"],
314 GOOS: meta["GOOS"],
315 GOARCH: meta["GOARCH"],
316 Counters: make(map[string]int64),
317 Stacks: make(map[string]int64),
318 }
319 report.Programs = append(report.Programs, &prog)
320 return &prog
321 }
322
323
324
325 func computeRandom() float64 {
326 for {
327 b := make([]byte, 8)
328 _, err := rand.Read(b)
329 if err != nil {
330 panic(fmt.Sprintf("rand.Read failed: %v", err))
331 }
332
333 x := math.Float64frombits(binary.LittleEndian.Uint64(b))
334 if math.IsNaN(x) || math.IsInf(x, 0) {
335 continue
336 }
337 x = math.Abs(x)
338 if x < 0x1p-1000 {
339 continue
340 }
341 frac, _ := math.Frexp(x)
342 return frac*2 - 1
343 }
344 }
345
View as plain text