liyujie
2025-08-28 786ff4f4ca2374bdd9177f2e24b503d43e7a3b93
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
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
 
package main
 
// This server runs along side the karma tests and listens for POST requests
// when any test case reports it has output for Perf. See perfReporter.js
// for the browser side part.
 
// Unlike the gold ingester, the perf ingester allows multiple reports
// of the same benchmark and will output the average of these results
// on a call to dump
 
import (
   "encoding/json"
   "flag"
   "fmt"
   "io/ioutil"
   "log"
   "net/http"
   "os"
   "path"
   "strconv"
   "strings"
 
   "github.com/google/uuid"
   "go.skia.org/infra/perf/go/ingestcommon"
)
 
// upload_nano_results looks for anything*.json
// We add the random UUID to avoid name clashes when uploading to
// the perf bucket (which uploads to folders based on Month/Day/Hour, which can
// easily have duplication if multiple perf tasks run in an hour.)
var JSON_FILENAME = fmt.Sprintf("%s_browser_bench.json", uuid.New().String())
 
var (
   outDir = flag.String("out_dir", "/OUT/", "location to dump the Perf JSON")
   port   = flag.String("port", "8081", "Port to listen on.")
 
   botId            = flag.String("bot_id", "", "swarming bot id")
   browser          = flag.String("browser", "Chrome", "Browser Key")
   buildBucketID    = flag.Int64("buildbucket_build_id", 0, "Buildbucket build id key")
   builder          = flag.String("builder", "", "Builder, like 'Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit'")
   compiledLanguage = flag.String("compiled_language", "wasm", "wasm or asm.js")
   config           = flag.String("config", "Release", "Configuration (e.g. Debug/Release) key")
   gitHash          = flag.String("git_hash", "-", "The git commit hash of the version being tested")
   hostOS           = flag.String("host_os", "Debian9", "OS Key")
   issue            = flag.Int64("issue", 0, "issue (if tryjob)")
   patch_storage    = flag.String("patch_storage", "", "patch storage (if tryjob)")
   patchset         = flag.Int64("patchset", 0, "patchset (if tryjob)")
   taskId           = flag.String("task_id", "", "swarming task id")
   sourceType       = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
)
 
// Received from the JS side.
type reportBody struct {
   // a name describing the benchmark. Should be unique enough to allow use of grep.
   BenchName string `json:"bench_name"`
   // The number of microseconds of the task.
   TimeMicroSeconds float64 `json:"time_us"`
}
 
// The keys to be used at the top level for all Results.
var defaultKeys map[string]string
 
// contains all the results reported in through report_perf_data
var results map[string][]reportBody
 
type BenchData struct {
   Hash         string                               `json:"gitHash"`
   Issue        string                               `json:"issue"`
   PatchSet     string                               `json:"patchset"`
   Key          map[string]string                    `json:"key"`
   Options      map[string]string                    `json:"options,omitempty"`
   Results      map[string]ingestcommon.BenchResults `json:"results"`
   PatchStorage string                               `json:"patch_storage,omitempty"`
 
   SwarmingTaskID string `json:"swarming_task_id,omitempty"`
   SwarmingBotID  string `json:"swarming_bot_id,omitempty"`
}
 
func main() {
   flag.Parse()
 
   cpuGPU := "CPU"
   if strings.Index(*builder, "-GPU-") != -1 {
       cpuGPU = "GPU"
   }
   defaultKeys = map[string]string{
       "arch":              "WASM",
       "browser":           *browser,
       "compiled_language": *compiledLanguage,
       "compiler":          "emsdk",
       "configuration":     *config,
       "cpu_or_gpu":        cpuGPU,
       "cpu_or_gpu_value":  "Browser",
       "os":                *hostOS,
       "source_type":       *sourceType,
   }
 
   results = make(map[string][]reportBody)
 
   http.HandleFunc("/report_perf_data", reporter)
   http.HandleFunc("/dump_json", dumpJSON)
 
   fmt.Printf("Waiting for perf ingestion on port %s\n", *port)
 
   log.Fatal(http.ListenAndServe(":"+*port, nil))
}
 
// reporter handles when the client reports a test has a benchmark.
func reporter(w http.ResponseWriter, r *http.Request) {
   if r.Method != "POST" {
       http.Error(w, "Only POST accepted", 400)
       return
   }
   defer r.Body.Close()
 
   body, err := ioutil.ReadAll(r.Body)
   if err != nil {
       http.Error(w, "Malformed body", 400)
       return
   }
 
   benchOutput := reportBody{}
   if err := json.Unmarshal(body, &benchOutput); err != nil {
       fmt.Println(err)
       http.Error(w, "Could not unmarshal JSON", 400)
       return
   }
 
   if _, err := w.Write([]byte("Accepted")); err != nil {
       fmt.Printf("Could not write response: %s\n", err)
       return
   }
 
   results[benchOutput.BenchName] = append(results[benchOutput.BenchName], benchOutput)
}
 
// createOutputFile creates a file and set permissions correctly.
func createOutputFile(p string) (*os.File, error) {
   outputFile, err := os.Create(p)
   if err != nil {
       return nil, fmt.Errorf("Could not open file %s on disk: %s", p, err)
   }
   // Make this accessible (and deletable) by all users
   if err = outputFile.Chmod(0666); err != nil {
       return nil, fmt.Errorf("Could not change permissions of file %s: %s", p, err)
   }
   return outputFile, nil
}
 
// dumpJSON writes out a JSON file with all the results, typically at the end of
// all the tests. If there is more than one result per benchmark, we report the average.
func dumpJSON(w http.ResponseWriter, r *http.Request) {
   if r.Method != "POST" {
       http.Error(w, "Only POST accepted", 400)
       return
   }
 
   p := path.Join(*outDir, JSON_FILENAME)
   outputFile, err := createOutputFile(p)
   defer outputFile.Close()
   if err != nil {
       fmt.Println(err)
       http.Error(w, "Could not open json file on disk", 500)
       return
   }
 
   benchData := BenchData{
       Hash:           *gitHash,
       Issue:          strconv.FormatInt(*issue, 10),
       PatchStorage:   *patch_storage,
       PatchSet:       strconv.FormatInt(*patchset, 10),
       Key:            defaultKeys,
       SwarmingBotID:  *botId,
       SwarmingTaskID: *taskId,
   }
 
   allResults := make(map[string]ingestcommon.BenchResults)
   for name, benches := range results {
       samples := []float64{}
       total := float64(0)
       for _, t := range benches {
           samples = append(samples, t.TimeMicroSeconds)
           total += t.TimeMicroSeconds
       }
       allResults[name] = map[string]ingestcommon.BenchResult{
           "default": map[string]interface{}{
               "average_us": total / float64(len(benches)),
               "samples":    samples,
           },
       }
   }
   benchData.Results = allResults
 
   enc := json.NewEncoder(outputFile)
   enc.SetIndent("", "  ") // Make it human readable.
   if err := enc.Encode(&benchData); err != nil {
       fmt.Println(err)
       http.Error(w, "Could not write json to disk", 500)
       return
   }
   fmt.Println("JSON Written")
}