upload.go (3622B)
1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package upload 6 7 import ( 8 "bytes" 9 "net/http" 10 "os" 11 "path/filepath" 12 "regexp" 13 "strings" 14 "time" 15 16 "golang.org/x/telemetry/internal/telemetry" 17 ) 18 19 var ( 20 dateRE = regexp.MustCompile(`(\d\d\d\d-\d\d-\d\d)[.]json$`) 21 dateFormat = telemetry.DateOnly 22 // TODO(rfindley): use dateFormat throughout. 23 ) 24 25 // uploadReportDate returns the date component of the upload file name, or "" if the 26 // date was unmatched. 27 func (u *uploader) uploadReportDate(fname string) time.Time { 28 match := dateRE.FindStringSubmatch(fname) 29 if match == nil || len(match) < 2 { 30 u.logger.Printf("malformed report name: missing date: %q", filepath.Base(fname)) 31 return time.Time{} 32 } 33 d, err := time.Parse(dateFormat, match[1]) 34 if err != nil { 35 u.logger.Printf("malformed report name: bad date: %q", filepath.Base(fname)) 36 return time.Time{} 37 } 38 return d 39 } 40 41 func (u *uploader) uploadReport(fname string) { 42 thisInstant := u.startTime 43 // TODO(rfindley): use uploadReportDate here, once we've done a gopls release. 44 45 // first make sure it is not in the future 46 today := thisInstant.Format(telemetry.DateOnly) 47 match := dateRE.FindStringSubmatch(fname) 48 if match == nil || len(match) < 2 { 49 u.logger.Printf("Report name %q missing date", filepath.Base(fname)) 50 } else if match[1] > today { 51 u.logger.Printf("Report date for %q is later than today (%s)", filepath.Base(fname), today) 52 return // report is in the future, which shouldn't happen 53 } 54 buf, err := os.ReadFile(fname) 55 if err != nil { 56 u.logger.Printf("%v reading %s", err, fname) 57 return 58 } 59 if u.uploadReportContents(fname, buf) { 60 // anything left to do? 61 } 62 } 63 64 // try to upload the report, 'true' if successful 65 func (u *uploader) uploadReportContents(fname string, buf []byte) bool { 66 fdate := strings.TrimSuffix(filepath.Base(fname), ".json") 67 fdate = fdate[len(fdate)-len(telemetry.DateOnly):] 68 69 newname := filepath.Join(u.dir.UploadDir(), fdate+".json") 70 71 // Lock the upload, to prevent duplicate uploads. 72 { 73 lockname := newname + ".lock" 74 lockfile, err := os.OpenFile(lockname, os.O_CREATE|os.O_EXCL, 0666) 75 if err != nil { 76 u.logger.Printf("Failed to acquire lock %s: %v", lockname, err) 77 return false 78 } 79 _ = lockfile.Close() 80 defer os.Remove(lockname) 81 } 82 83 if _, err := os.Stat(newname); err == nil { 84 // Another process uploaded but failed to clean up (or hasn't yet cleaned 85 // up). Ensure that cleanup occurs. 86 u.logger.Printf("After acquire: report already uploaded") 87 _ = os.Remove(fname) 88 return false 89 } 90 91 endpoint := u.uploadServerURL + "/" + fdate 92 b := bytes.NewReader(buf) 93 resp, err := http.Post(endpoint, "application/json", b) 94 if err != nil { 95 u.logger.Printf("Error upload %s to %s: %v", filepath.Base(fname), endpoint, err) 96 return false 97 } 98 // hope for a 200, remove file on a 4xx, otherwise it will be retried by another process 99 if resp.StatusCode != 200 { 100 u.logger.Printf("Failed to upload %s to %s: %s", filepath.Base(fname), endpoint, resp.Status) 101 if resp.StatusCode >= 400 && resp.StatusCode < 500 { 102 err := os.Remove(fname) 103 if err == nil { 104 u.logger.Printf("Removed local/%s", filepath.Base(fname)) 105 } else { 106 u.logger.Printf("Error removing local/%s: %v", filepath.Base(fname), err) 107 } 108 } 109 return false 110 } 111 // Store a copy of the uploaded report in the uploaded directory. 112 if err := os.WriteFile(newname, buf, 0644); err == nil { 113 os.Remove(fname) // if it exists 114 } 115 u.logger.Printf("Uploaded %s to %q", fdate+".json", endpoint) 116 return true 117 }