Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(567)

Side by Side Diff: infra/bots/gen_tasks.go

Issue 2386663002: [task scheduler] Add gen_tasks.go (Closed)
Patch Set: Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | infra/bots/tasks.json » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package main
6
7 /*
8 Generate the tasks.json file.
9 */
10
11 import (
12 "bytes"
13 "encoding/json"
14 "fmt"
15 "io/ioutil"
16 "os"
17 "path"
18 "path/filepath"
19 "strings"
20
21 "github.com/skia-dev/glog"
22 "go.skia.org/infra/go/common"
23 "go.skia.org/infra/go/util"
24 "go.skia.org/infra/task_scheduler/go/specs"
25 )
26
27 const (
28 DEFAULT_OS = "Ubuntu"
29
30 // Pool for Skia bots.
31 POOL_SKIA = "Skia"
32
33 // Name prefix for upload jobs.
34 PREFIX_UPLOAD = "Upload"
35 )
36
37 var (
38 // "Constants"
39
40 // Top-level list of all jobs to run at each commit.
41 JOBS = []string{
42 "Build-Ubuntu-GCC-x86_64-Release-GN",
43 "Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-GN",
44 "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-GN",
45 }
46
47 // UPLOAD_DIMENSIONS are the Swarming dimensions for upload tasks.
48 UPLOAD_DIMENSIONS = []string{
49 fmt.Sprintf("pool:%s", POOL_SKIA),
50 "os:Ubuntu",
51 "gpu:none",
52 "cpu:x86-64-avx2",
53 }
54
55 // Defines the structure of job names.
56 jobNameSchema *JobNameSchema
57
58 // Caches CIPD package info so that we don't have to re-read VERSION
59 // files.
60 cipdPackages = map[string]*specs.CipdPackage{}
61
62 // Path to the infra/bots directory.
63 infrabotsDir = ""
64 )
65
66 // deriveCompileTaskName returns the name of a compile task based on the given
67 // job name.
68 func deriveCompileTaskName(jobName string, parts map[string]string) string {
69 if parts["role"] == "Housekeeper" {
70 return "Build-Ubuntu-GCC-x86_64-Release-Shared"
71 } else if parts["role"] == "Test" || parts["role"] == "Perf" {
72 task_os := parts["os"]
73 ec := parts["extra_config"]
74 if task_os == "Android" {
dogben 2016/09/30 17:35:14 Did you intend to set task_os in this if?
borenet 2016/09/30 19:13:16 Yep, thanks!
75 if ec == "Vulkan" {
76 ec = "Android_Vulkan"
77 } else if !strings.Contains(ec, "GN_Android") {
78 ec = task_os
79 }
80 } else if task_os == "iOS" {
81 ec = task_os
82 task_os = "Mac"
83 } else if strings.Contains(task_os, "Win") {
84 task_os = "Win"
85 }
86 name, err := jobNameSchema.MakeJobName(map[string]string{
87 "role": "Build",
88 "os": task_os,
89 "compiler": parts["compiler"],
90 "target_arch": parts["arch"],
91 "configuration": parts["configuration"],
92 "extra_config": ec,
93 })
94 if err != nil {
95 glog.Fatal(err)
96 }
97 return name
98 } else {
99 return jobName
100 }
101 }
102
103 // swarmDimensions generates swarming bot dimensions for the given task.
104 func swarmDimensions(parts map[string]string) []string {
105 if parts["extra_config"] == "SkiaCT" {
106 return []string{
107 "pool:SkiaCT",
108 }
109 }
110 d := map[string]string{
111 "pool": "Skia",
dogben 2016/09/30 17:35:14 nit: fmt.Sprintf("pool:%s", POOL_SKIA) (Although
borenet 2016/09/30 19:13:16 Done-ish. Kept POOL_SKIA since it's used in multip
112 }
113 if os, ok := parts["os"]; ok {
114 d["os"] = os
115 } else {
116 d["os"] = DEFAULT_OS
117 }
118 if strings.Contains(d["os"], "Win") {
119 d["os"] = "Windows"
120 }
121 if parts["role"] == "Test" || parts["role"] == "Perf" {
122 if strings.Contains(parts["os"], "Android") {
123 // For Android, the device type is a better dimension
124 // than CPU or GPU.
125 d["device_type"] = map[string]string{
126 "AndroidOne": "sprout",
127 "GalaxyS3": "m0", // "smdk4x12", Detected i ncorrectly by swarming?
128 "GalaxyS4": "", // TODO(borenet,kjlubick)
dogben 2016/09/30 17:35:14 In swarm_trigger.py, this is None, which I'm guess
borenet 2016/09/30 19:13:16 Yeah, I'm not sure what the behavior is in either
129 "GalaxyS7": "heroqlteatt",
130 "NVIDIA_Shield": "foster",
131 "Nexus10": "manta",
132 "Nexus5": "hammerhead",
133 "Nexus6": "shamu",
134 "Nexus6p": "angler",
135 "Nexus7": "grouper",
136 "Nexus7v2": "flo",
137 "Nexus9": "flounder",
138 "NexusPlayer": "fugu",
139 }[parts["model"]]
140 } else if strings.Contains(parts["os"], "iOS") {
141 d["device"] = map[string]string{
142 "iPad4": "iPad4,1",
143 }[parts["model"]]
144 // TODO(borenet): Replace this hack with something
145 // better.
146 d["os"] = "iOS-9.2"
147 } else if parts["cpu_or_gpu"] == "CPU" {
148 d["gpu"] = "none"
149 d["cpu"] = map[string]string{
150 "AVX": "x86-64",
151 "AVX2": "x86-64-avx2",
152 "SSE4": "x86-64",
153 }[parts["cpu_or_gpu_value"]]
154 if strings.Contains(parts["os"], "Win") && parts["cpu_or _gpu_value"] == "AVX2" {
155 // AVX2 is not correctly detected on Windows. Fa ll back on other
156 // dimensions to ensure that we correctly target machines which we know
157 // have AVX2 support.
158 d["cpu"] = "x86-64"
159 d["os"] = "Windows-2008ServerR2-SP1"
160 }
161 } else {
162 d["gpu"] = map[string]string{
163 "GeForce320M": "10de:08a4",
164 "GT610": "10de:104a",
165 "GTX550Ti": "10de:1244",
166 "GTX660": "10de:11c0",
167 "GTX960": "10de:1401",
168 "HD4000": "8086:0a2e",
169 "HD4600": "8086:0412",
170 "HD7770": "1002:683d",
171 "iHD530": "8086:1912",
172 }[parts["cpu_or_gpu_value"]]
173 }
174 } else {
175 d["gpu"] = "none"
176 }
177 rv := make([]string, 0, len(d))
178 for k, v := range d {
179 rv = append(rv, fmt.Sprintf("%s:%s", k, v))
180 }
181 return rv
dogben 2016/09/30 17:35:14 Sort?
borenet 2016/09/30 19:13:16 Done.
182 }
183
184 // getCipdPackage finds and returns the given CIPD package and version.
185 func getCipdPackage(assetName string) *specs.CipdPackage {
186 if pkg, ok := cipdPackages[assetName]; ok {
187 return pkg
188 }
189 versionFile := path.Join(infrabotsDir, "assets", assetName, "VERSION")
190 contents, err := ioutil.ReadFile(versionFile)
191 if err != nil {
192 glog.Fatal(err)
193 }
194 version := strings.TrimSpace(string(contents))
195 pkg := &specs.CipdPackage{
196 Name: fmt.Sprintf("skia/bots/%s", assetName),
197 Path: assetName,
198 Version: fmt.Sprintf("version:%s", version),
199 }
200 if assetName == "win_toolchain" {
201 pkg.Path = "t" // Workaround for path length limit on Windows.
202 }
203 cipdPackages[assetName] = pkg
204 return pkg
205 }
206
207 // compile generates a compile task.
208 func compile(cfg *specs.TasksCfg, name string, parts map[string]string) string {
209 // Collect the necessary CIPD packages.
210 pkgs := []*specs.CipdPackage{}
211
212 // Android bots require a toolchain.
213 if strings.Contains(name, "Android") {
214 pkgs = append(pkgs, getCipdPackage("android_sdk"))
215 if strings.Contains(name, "Mac") {
216 pkgs = append(pkgs, getCipdPackage("android_ndk_darwin") )
217 } else {
218 pkgs = append(pkgs, getCipdPackage("android_ndk_linux"))
219 }
220 }
221
222 // Clang on Linux.
223 if strings.Contains(name, "Ubuntu") && strings.Contains(name, "Clang") {
224 pkgs = append(pkgs, getCipdPackage("clang_linux"))
225 }
226
227 // Windows toolchain.
228 if strings.Contains(name, "Win") {
229 pkgs = append(pkgs, getCipdPackage("win_toolchain"))
230 if strings.Contains(name, "Vulkan") {
231 pkgs = append(pkgs, getCipdPackage("win_vulkan_sdk"))
232 }
233 }
234
235 // Add the task.
236 cfg.Tasks[name] = &specs.TaskSpec{
237 CipdPackages: pkgs,
238 Dimensions: swarmDimensions(parts),
239 ExtraArgs: []string{
240 "--workdir", "../../..", "swarm_compile",
241 fmt.Sprintf("buildername=%s", name),
242 "mastername=fake-master",
243 "buildnumber=2",
244 "slavename=fake-buildslave",
245 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLAT ED_OUTDIR),
246 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
247 },
248 Isolate: "compile_skia.isolate",
249 Priority: 0.8,
250 }
251 return name
252 }
253
254 // recreateSKPs generates a RecreateSKPs task.
255 func recreateSKPs(cfg *specs.TasksCfg, name string) string {
256 // TODO
257 return name
258 }
259
260 // ctSKPs generates a CT SKPs task.
261 func ctSKPs(cfg *specs.TasksCfg, name string) string {
262 // TODO
263 return name
264 }
265
266 // housekeeper generates a Housekeeper task.
267 func housekeeper(cfg *specs.TasksCfg, name, compileTaskName string) string {
268 // TODO
269 return name
270 }
271
272 // test generates a Test task.
dogben 2016/09/30 17:35:14 nit: say what return value is same for perf
borenet 2016/09/30 19:13:16 Done here and elsewhere.
273 func test(cfg *specs.TasksCfg, name string, parts map[string]string, compileTask Name string, pkgs []*specs.CipdPackage) string {
274 cfg.Tasks[name] = &specs.TaskSpec{
275 CipdPackages: pkgs,
276 Dependencies: []string{compileTaskName},
277 Dimensions: swarmDimensions(parts),
278 ExtraArgs: []string{
279 "--workdir", "../../..", "swarm_test",
280 fmt.Sprintf("buildername=%s", name),
281 "mastername=fake-master",
282 "buildnumber=2",
283 "slavename=fake-buildslave",
284 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLAT ED_OUTDIR),
285 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
286 },
287 Isolate: "test_skia.isolate",
288 Priority: 0.8,
289 }
290 // Upload results if necessary.
291 skipUploadBots := []string{
292 "ASAN",
293 "Coverage",
294 "MSAN",
295 "TSAN",
296 "UBSAN",
297 "Valgrind",
298 }
299 upload := true
300 for _, s := range skipUploadBots {
301 if strings.Contains(name, s) {
302 upload = false
303 break
304 }
305 }
306 if upload {
307 uploadName := fmt.Sprintf("%s%s%s", PREFIX_UPLOAD, jobNameSchema .Sep, name)
308 cfg.Tasks[uploadName] = &specs.TaskSpec{
309 Dependencies: []string{name},
310 Dimensions: UPLOAD_DIMENSIONS,
311 ExtraArgs: []string{
312 "--workdir", "../../..", "upload_dm_results",
313 fmt.Sprintf("buildername=%s", name),
314 "mastername=fake-master",
315 "buildnumber=2",
316 "slavename=fake-buildslave",
317 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDE R_ISOLATED_OUTDIR),
318 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REV ISION),
319 },
320 Isolate: "upload_dm_results.isolate",
321 Priority: 0.8,
322 }
323 return uploadName
324 }
325 return name
326 }
327
328 // perf generates a Perf task.
329 func perf(cfg *specs.TasksCfg, name string, parts map[string]string, compileTask Name string, pkgs []*specs.CipdPackage) string {
330 cfg.Tasks[name] = &specs.TaskSpec{
331 CipdPackages: pkgs,
332 Dependencies: []string{compileTaskName},
333 Dimensions: swarmDimensions(parts),
334 ExtraArgs: []string{
335 "--workdir", "../../..", "swarm_perf",
336 fmt.Sprintf("buildername=%s", name),
337 "mastername=fake-master",
338 "buildnumber=2",
339 "slavename=fake-buildslave",
340 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLAT ED_OUTDIR),
341 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
342 },
343 Isolate: "perf_skia.isolate",
344 Priority: 0.8,
345 }
346 // Upload results if necessary.
347 if strings.Contains(name, "Release") {
dogben 2016/09/30 17:35:14 This agrees with the recipe, but just so you're aw
borenet 2016/09/30 19:13:16 Ouch. That's a bug. Fixed here, and will make a se
348 uploadName := fmt.Sprintf("%s%s%s", PREFIX_UPLOAD, jobNameSchema .Sep, name)
349 cfg.Tasks[uploadName] = &specs.TaskSpec{
350 Dependencies: []string{name},
351 Dimensions: UPLOAD_DIMENSIONS,
352 ExtraArgs: []string{
353 "--workdir", "../../..", "upload_dm_results",
dogben 2016/09/30 17:35:14 s/dm/nano/
borenet 2016/09/30 19:13:16 Done.
354 fmt.Sprintf("buildername=%s", name),
355 "mastername=fake-master",
356 "buildnumber=2",
357 "slavename=fake-buildslave",
358 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDE R_ISOLATED_OUTDIR),
359 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REV ISION),
360 },
361 Isolate: "upload_dm_results.isolate",
362 Priority: 0.8,
363 }
364 return uploadName
365 }
366 return name
367 }
368
369 // process generates tasks and jobs for the given job name.
370 func process(cfg *specs.TasksCfg, name string) {
371 if _, ok := cfg.Jobs[name]; ok {
372 glog.Fatalf("Duplicate job %q", name)
373 }
374 deps := []string{}
375
376 parts, err := jobNameSchema.ParseJobName(name)
377 if err != nil {
378 glog.Fatal(err)
379 }
380
381 // RecreateSKPs.
382 if strings.Contains(name, "RecreateSKPs") {
383 deps = append(deps, recreateSKPs(cfg, name))
384 }
385
386 // CT bots.
387 if strings.Contains(name, "-CT_") {
388 deps = append(deps, ctSKPs(cfg, name))
389 }
390
391 // Compile bots.
392 if parts["role"] == "Build" {
393 deps = append(deps, compile(cfg, name, parts))
394 }
395
396 // Any remaining bots need a compile task.
397 compileTaskName := deriveCompileTaskName(name, parts)
dogben 2016/09/30 17:35:14 This seems to assume we will have Build jobs for e
borenet 2016/09/30 19:13:16 Yeah, that was my intention, although I can defini
398
399 // Housekeeper.
400 if parts["role"] == "Housekeeper" {
401 deps = append(deps, housekeeper(cfg, name, compileTaskName))
402 }
403
404 // Common assets needed by the remaining bots.
405 pkgs := []*specs.CipdPackage{
406 getCipdPackage("skimage"),
407 getCipdPackage("skp"),
408 getCipdPackage("svg"),
409 }
410
411 // Test bots.
412 if parts["role"] == "Test" {
413 deps = append(deps, test(cfg, name, parts, compileTaskName, pkgs ))
414 }
415
416 // Perf bots.
417 if parts["role"] == "Perf" {
418 deps = append(deps, perf(cfg, name, parts, compileTaskName, pkgs ))
419 }
420
421 // Add the Job spec.
422 cfg.Jobs[name] = &specs.JobSpec{
423 Priority: 0.8,
424 TaskSpecs: deps,
425 }
426 }
427
428 // getCheckoutRoot returns the path of the root of the Skia checkout, or an
429 // error if it cannot be found.
430 func getCheckoutRoot() string {
431 cwd, err := os.Getwd()
432 if err != nil {
433 glog.Fatal(err)
434 }
435 for {
436 if _, err := os.Stat(cwd); err != nil {
437 glog.Fatal(err)
438 }
439 s, err := os.Stat(path.Join(cwd, ".git"))
440 if err == nil && s.IsDir() {
441 // TODO(borenet): Should we verify that this is a Skia
442 // checkout and not something else?
443 return cwd
444 }
445 cwd = filepath.Clean(path.Join(cwd, ".."))
446 }
447 }
448
449 // Regenerate the tasks.json file.
450 func main() {
451 common.Init()
452 defer common.LogPanic()
453
454 // Where are we?
455 root := getCheckoutRoot()
456 infrabotsDir = path.Join(root, "infra", "bots")
457
458 // Create the JobNameSchema.
459 schema, err := NewJobNameSchema(path.Join(infrabotsDir, "recipe_modules" , "builder_name_schema", "builder_name_schema.json"))
460 if err != nil {
461 glog.Fatal(err)
462 }
463 jobNameSchema = schema
464
465 // Create the config.
466 cfg := &specs.TasksCfg{
467 Jobs: map[string]*specs.JobSpec{},
468 Tasks: map[string]*specs.TaskSpec{},
469 }
470
471 // Create Tasks and Jobs.
472 for _, j := range JOBS {
473 process(cfg, j)
474 }
475
476 // Validate the config.
477 if err := cfg.Validate(); err != nil {
478 glog.Fatal(err)
479 }
480
481 // Write the tasks.json file.
482 outFile := path.Join(root, specs.TASKS_CFG_FILE)
483 b, err := json.MarshalIndent(cfg, "", " ")
484 if err != nil {
485 glog.Fatal(err)
486 }
487 b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
dogben 2016/09/30 17:35:14 Please add a comment.
borenet 2016/09/30 19:13:16 Done.
488 if err := ioutil.WriteFile(outFile, b, os.ModePerm); err != nil {
489 glog.Fatal(err)
490 }
491 }
492
493 // TODO(borenet): The below really belongs in its own file, probably next to the
494 // builder_name_schema.json file.
495
496 // JobNameSchema is a struct used for (de)constructing Job names in a
497 // predictable format.
498 type JobNameSchema struct {
499 Schema map[string][]string `json:"builder_name_schema"`
500 Sep string `json:"builder_name_sep"`
501 }
502
503 // NewJobNameSchema returns a JobNameSchema instance based on the given JSON
504 // file.
505 func NewJobNameSchema(jsonFile string) (*JobNameSchema, error) {
506 var rv JobNameSchema
507 f, err := os.Open(jsonFile)
508 if err != nil {
509 return nil, err
510 }
511 defer util.Close(f)
512 if err := json.NewDecoder(f).Decode(&rv); err != nil {
513 return nil, err
514 }
515 return &rv, nil
516 }
517
518 // ParseJobName splits the given Job name into its component parts, according
519 // to the schema.
520 func (s *JobNameSchema) ParseJobName(n string) (map[string]string, error) {
521 split := strings.Split(n, s.Sep)
522 if len(split) < 2 {
523 return nil, fmt.Errorf("Invalid job name: %q", n)
524 }
525 role := split[0]
526 split = split[1:]
527 keys, ok := s.Schema[role]
528 if !ok {
529 return nil, fmt.Errorf("Invalid job name; %q is not a valid role .", role)
530 }
531 extraConfig := ""
532 if len(split) == len(keys)+1 {
533 extraConfig = split[len(split)-1]
534 split = split[:len(split)-1]
535 }
536 if len(split) != len(keys) {
537 return nil, fmt.Errorf("Invalid job name; %q has incorrect numbe r of parts.", n)
538 }
539 rv := make(map[string]string, len(keys)+2)
540 rv["role"] = role
541 if extraConfig != "" {
542 rv["extra_config"] = extraConfig
543 }
544 for i, k := range keys {
545 rv[k] = split[i]
546 }
547 return rv, nil
548 }
549
550 // MakeJobName assembles the given parts of a Job name, according to the schema.
551 func (s *JobNameSchema) MakeJobName(parts map[string]string) (string, error) {
552 role, ok := parts["role"]
553 if !ok {
554 return "", fmt.Errorf("Invalid job parts; jobs must have a role. ")
555 }
556 keys, ok := s.Schema[role]
557 if !ok {
558 return "", fmt.Errorf("Invalid job parts; unknown role %q", role )
559 }
560 rvParts := make([]string, 0, len(parts))
561 rvParts = append(rvParts, role)
562 for _, k := range keys {
563 v, ok := parts[k]
564 if !ok {
565 return "", fmt.Errorf("Invalid job parts; missing %q", k )
566 }
567 rvParts = append(rvParts, v)
568 }
569 if _, ok := parts["extra_config"]; ok {
570 rvParts = append(rvParts, parts["extra_config"])
571 }
572 return strings.Join(rvParts, s.Sep), nil
573 }
OLDNEW
« no previous file with comments | « no previous file | infra/bots/tasks.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698