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

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

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