blob: c079e8388a7d85ca5cca0ab2134c2af1151fb50a [file] [log] [blame]
Dan Willemsenc2af0be2017-01-20 14:10:01 -08001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
Dan Willemsenc2af0be2017-01-20 14:10:01 -080018 "context"
19 "flag"
20 "fmt"
Dan Willemsen41538382018-08-31 19:51:35 -070021 "io"
Dan Willemsenf624fb92017-05-19 16:39:04 -070022 "io/ioutil"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080023 "os"
24 "path/filepath"
25 "runtime"
26 "strings"
27 "sync"
Dan Willemsen22de2162017-12-11 14:35:23 -080028 "syscall"
Steven Moreland552432e2017-03-29 19:26:09 -070029 "time"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080030
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -070031 "android/soong/finder"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080032 "android/soong/ui/build"
33 "android/soong/ui/logger"
Dan Willemsenb82471a2018-05-17 16:37:09 -070034 "android/soong/ui/status"
35 "android/soong/ui/terminal"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070036 "android/soong/ui/tracer"
Dan Willemsene3480762017-11-07 11:23:27 -080037 "android/soong/zip"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080038)
39
Dan Willemsen1bdbdec2019-12-27 09:54:11 -080040var numJobs = flag.Int("j", 0, "number of parallel jobs [0=autodetect]")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080041
Dan Willemsene3480762017-11-07 11:23:27 -080042var keepArtifacts = flag.Bool("keep", false, "keep archives of artifacts")
Dan Willemsen41538382018-08-31 19:51:35 -070043var incremental = flag.Bool("incremental", false, "run in incremental mode (saving intermediates)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080044
45var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
Dan Willemsenf624fb92017-05-19 16:39:04 -070046var alternateResultDir = flag.Bool("dist", false, "write select results to $DIST_DIR (or <out>/dist when empty)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080047
48var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
49var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
50
Dan Willemsen5ed900b2017-05-07 11:40:30 -070051var buildVariant = flag.String("variant", "eng", "build variant to use")
52
Dan Willemsen9957b9c2017-10-06 15:05:05 -070053var skipProducts = flag.String("skip-products", "", "comma-separated list of products to skip (known failures, etc)")
Jeff Gastonb61e3f72017-10-25 15:02:45 -070054var includeProducts = flag.String("products", "", "comma-separated list of products to build")
Dan Willemsen9957b9c2017-10-06 15:05:05 -070055
Dan Willemsen9609ad92019-12-05 15:22:41 -080056var shardCount = flag.Int("shard-count", 1, "split the products into multiple shards (to spread the build onto multiple machines, etc)")
57var shard = flag.Int("shard", 1, "1-indexed shard to execute")
58
Dan Willemsenf624fb92017-05-19 16:39:04 -070059const errorLeadingLines = 20
60const errorTrailingLines = 20
61
Dan Willemsenb82471a2018-05-17 16:37:09 -070062func errMsgFromLog(filename string) string {
63 if filename == "" {
64 return ""
Dan Willemsena4e43a72017-05-06 16:58:26 -070065 }
66
Dan Willemsenb82471a2018-05-17 16:37:09 -070067 data, err := ioutil.ReadFile(filename)
68 if err != nil {
69 return ""
Dan Willemsenf624fb92017-05-19 16:39:04 -070070 }
71
Dan Willemsenb82471a2018-05-17 16:37:09 -070072 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
73 if len(lines) > errorLeadingLines+errorTrailingLines+1 {
74 lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
75 len(lines)-errorLeadingLines-errorTrailingLines)
Dan Willemsena4e43a72017-05-06 16:58:26 -070076
Dan Willemsenb82471a2018-05-17 16:37:09 -070077 lines = append(lines[:errorLeadingLines+1],
78 lines[len(lines)-errorTrailingLines:]...)
Dan Willemsena4e43a72017-05-06 16:58:26 -070079 }
Dan Willemsenb82471a2018-05-17 16:37:09 -070080 var buf strings.Builder
81 for _, line := range lines {
82 buf.WriteString("> ")
83 buf.WriteString(line)
84 buf.WriteString("\n")
Dan Willemsena4e43a72017-05-06 16:58:26 -070085 }
Dan Willemsenb82471a2018-05-17 16:37:09 -070086 return buf.String()
Dan Willemsena4e43a72017-05-06 16:58:26 -070087}
88
Dan Willemsen22de2162017-12-11 14:35:23 -080089// TODO(b/70370883): This tool uses a lot of open files -- over the default
90// soft limit of 1024 on some systems. So bump up to the hard limit until I fix
91// the algorithm.
92func setMaxFiles(log logger.Logger) {
93 var limits syscall.Rlimit
94
95 err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
96 if err != nil {
97 log.Println("Failed to get file limit:", err)
98 return
99 }
100
101 log.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
102 if limits.Cur == limits.Max {
103 return
104 }
105
106 limits.Cur = limits.Max
107 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
108 if err != nil {
109 log.Println("Failed to increase file limit:", err)
110 }
111}
112
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700113func inList(str string, list []string) bool {
114 for _, other := range list {
115 if str == other {
116 return true
117 }
118 }
119 return false
120}
121
Dan Willemsen41538382018-08-31 19:51:35 -0700122func copyFile(from, to string) error {
123 fromFile, err := os.Open(from)
124 if err != nil {
125 return err
126 }
127 defer fromFile.Close()
128
129 toFile, err := os.Create(to)
130 if err != nil {
131 return err
132 }
133 defer toFile.Close()
134
135 _, err = io.Copy(toFile, fromFile)
136 return err
137}
138
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700139type mpContext struct {
140 Context context.Context
141 Logger logger.Logger
142 Status status.ToolStatus
143 Tracer tracer.Tracer
144 Finder *finder.Finder
145 Config build.Config
146
147 LogsDir string
148}
149
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800150func main() {
Colin Cross097ed2a2019-06-08 21:48:58 -0700151 stdio := terminal.StdioImpl{}
Dan Willemsenb82471a2018-05-17 16:37:09 -0700152
Colin Crossc0b9f6b2019-09-23 12:44:54 -0700153 output := terminal.NewStatusOutput(stdio.Stdout(), "", false,
Colin Crosse0df1a32019-06-09 19:40:08 -0700154 build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
155
156 log := logger.New(output)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800157 defer log.Cleanup()
158
159 flag.Parse()
160
161 ctx, cancel := context.WithCancel(context.Background())
162 defer cancel()
163
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700164 trace := tracer.New(log)
165 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800166
Dan Willemsenb82471a2018-05-17 16:37:09 -0700167 stat := &status.Status{}
168 defer stat.Finish()
Colin Crosse0df1a32019-06-09 19:40:08 -0700169 stat.AddOutput(output)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700170
Dan Willemsen34218ec2018-07-19 22:57:18 -0700171 var failures failureCount
172 stat.AddOutput(&failures)
173
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700174 build.SetupSignals(log, cancel, func() {
175 trace.Close()
176 log.Cleanup()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700177 stat.Finish()
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700178 })
179
Dan Willemsen59339a22018-07-22 21:18:45 -0700180 buildCtx := build.Context{ContextImpl: &build.ContextImpl{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700181 Context: ctx,
182 Logger: log,
183 Tracer: trace,
Colin Crosse0df1a32019-06-09 19:40:08 -0700184 Writer: output,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700185 Status: stat,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700186 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800187
Rupert Shuttleworth3c9f5ac2020-12-10 11:32:38 +0000188 args := ""
189 if *alternateResultDir {
190 args = "dist"
191 }
192 config := build.NewConfig(buildCtx, args)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800193 if *outDir == "" {
Dan Willemsen41538382018-08-31 19:51:35 -0700194 name := "multiproduct"
195 if !*incremental {
196 name += "-" + time.Now().Format("20060102150405")
197 }
Steven Moreland552432e2017-03-29 19:26:09 -0700198
199 *outDir = filepath.Join(config.OutDir(), name)
200
Dan Willemsenf624fb92017-05-19 16:39:04 -0700201 // Ensure the empty files exist in the output directory
202 // containing our output directory too. This is mostly for
203 // safety, but also triggers the ninja_build file so that our
204 // build servers know that they can parse the output as if it
205 // was ninja output.
206 build.SetupOutDir(buildCtx, config)
207
Steven Moreland552432e2017-03-29 19:26:09 -0700208 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800209 log.Fatalf("Failed to create tempdir: %v", err)
210 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800211 }
212 config.Environment().Set("OUT_DIR", *outDir)
213 log.Println("Output directory:", *outDir)
214
Dan Willemsene3480762017-11-07 11:23:27 -0800215 logsDir := filepath.Join(config.OutDir(), "logs")
216 os.MkdirAll(logsDir, 0777)
217
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800218 build.SetupOutDir(buildCtx, config)
Rupert Shuttleworth3c9f5ac2020-12-10 11:32:38 +0000219
220 os.MkdirAll(config.LogsDir(), 0777)
221 log.SetOutput(filepath.Join(config.LogsDir(), "soong.log"))
222 trace.SetOutput(filepath.Join(config.LogsDir(), "build.trace"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800223
Dan Willemsen1bdbdec2019-12-27 09:54:11 -0800224 var jobs = *numJobs
225 if jobs < 1 {
226 jobs = runtime.NumCPU() / 4
227
228 ramGb := int(config.TotalRAM() / 1024 / 1024 / 1024)
229 if ramJobs := ramGb / 20; ramGb > 0 && jobs > ramJobs {
230 jobs = ramJobs
231 }
232
233 if jobs < 1 {
234 jobs = 1
235 }
236 }
237 log.Verbosef("Using %d parallel jobs", jobs)
238
Dan Willemsen22de2162017-12-11 14:35:23 -0800239 setMaxFiles(log)
240
Dan Willemsen04d76ef2018-05-15 00:52:29 -0700241 finder := build.NewSourceFinder(buildCtx, config)
242 defer finder.Shutdown()
243
244 build.FindSources(buildCtx, config, finder)
245
Dan Willemsenb2e6c2e2017-07-13 17:24:44 -0700246 vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800247 if err != nil {
248 log.Fatal(err)
249 }
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700250 var productsList []string
251 allProducts := strings.Fields(vars["all_named_products"])
252
253 if *includeProducts != "" {
254 missingProducts := []string{}
255 for _, product := range strings.Split(*includeProducts, ",") {
256 if inList(product, allProducts) {
257 productsList = append(productsList, product)
258 } else {
259 missingProducts = append(missingProducts, product)
260 }
261 }
262 if len(missingProducts) > 0 {
263 log.Fatalf("Products don't exist: %s\n", missingProducts)
264 }
265 } else {
266 productsList = allProducts
267 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700268
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700269 finalProductsList := make([]string, 0, len(productsList))
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700270 skipList := strings.Split(*skipProducts, ",")
271 skipProduct := func(p string) bool {
272 for _, s := range skipList {
273 if p == s {
274 return true
275 }
276 }
277 return false
278 }
279 for _, product := range productsList {
280 if !skipProduct(product) {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700281 finalProductsList = append(finalProductsList, product)
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700282 } else {
283 log.Verbose("Skipping: ", product)
284 }
285 }
286
Dan Willemsen9609ad92019-12-05 15:22:41 -0800287 if *shard < 1 {
288 log.Fatalf("--shard value must be >= 1, not %d\n", *shard)
289 } else if *shardCount < 1 {
290 log.Fatalf("--shard-count value must be >= 1, not %d\n", *shardCount)
291 } else if *shard > *shardCount {
292 log.Fatalf("--shard (%d) must not be greater than --shard-count (%d)\n", *shard,
293 *shardCount)
294 } else if *shardCount > 1 {
295 finalProductsList = splitList(finalProductsList, *shardCount)[*shard-1]
296 }
297
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700298 log.Verbose("Got product list: ", finalProductsList)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800299
Dan Willemsenb82471a2018-05-17 16:37:09 -0700300 s := buildCtx.Status.StartTool()
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700301 s.SetTotalActions(len(finalProductsList))
Dan Willemsena4e43a72017-05-06 16:58:26 -0700302
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700303 mpCtx := &mpContext{
304 Context: ctx,
305 Logger: log,
306 Status: s,
307 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800308
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700309 Finder: finder,
310 Config: config,
Dan Willemsenf624fb92017-05-19 16:39:04 -0700311
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700312 LogsDir: logsDir,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800313 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700314
315 products := make(chan string, len(productsList))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800316 go func() {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700317 defer close(products)
318 for _, product := range finalProductsList {
319 products <- product
320 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800321 }()
322
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700323 var wg sync.WaitGroup
Dan Willemsen1bdbdec2019-12-27 09:54:11 -0800324 for i := 0; i < jobs; i++ {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700325 wg.Add(1)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800326 go func() {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700327 defer wg.Done()
328 for {
329 select {
330 case product := <-products:
331 if product == "" {
332 return
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800333 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700334 buildProduct(mpCtx, product)
335 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800336 }
337 }()
338 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700339 wg.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800340
Dan Willemsene3480762017-11-07 11:23:27 -0800341 if *alternateResultDir {
342 args := zip.ZipArgs{
343 FileArgs: []zip.FileArg{
344 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
345 },
Rupert Shuttleworth3c9f5ac2020-12-10 11:32:38 +0000346 OutputFilePath: filepath.Join(config.RealDistDir(), "logs.zip"),
Dan Willemsene3480762017-11-07 11:23:27 -0800347 NumParallelJobs: runtime.NumCPU(),
348 CompressionLevel: 5,
349 }
Colin Cross05518bc2018-09-27 15:06:19 -0700350 if err := zip.Zip(args); err != nil {
Dan Willemsene3480762017-11-07 11:23:27 -0800351 log.Fatalf("Error zipping logs: %v", err)
352 }
353 }
354
Dan Willemsenb82471a2018-05-17 16:37:09 -0700355 s.Finish()
Dan Willemsen34218ec2018-07-19 22:57:18 -0700356
357 if failures == 1 {
358 log.Fatal("1 failure")
359 } else if failures > 1 {
360 log.Fatalf("%d failures", failures)
361 } else {
Colin Crosse0df1a32019-06-09 19:40:08 -0700362 fmt.Fprintln(output, "Success")
Dan Willemsen34218ec2018-07-19 22:57:18 -0700363 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800364}
Dan Willemsen34218ec2018-07-19 22:57:18 -0700365
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700366func buildProduct(mpctx *mpContext, product string) {
367 var stdLog string
368
369 outDir := filepath.Join(mpctx.Config.OutDir(), product)
370 logsDir := filepath.Join(mpctx.LogsDir, product)
371
372 if err := os.MkdirAll(outDir, 0777); err != nil {
373 mpctx.Logger.Fatalf("Error creating out directory: %v", err)
374 }
375 if err := os.MkdirAll(logsDir, 0777); err != nil {
376 mpctx.Logger.Fatalf("Error creating log directory: %v", err)
377 }
378
379 stdLog = filepath.Join(logsDir, "std.log")
380 f, err := os.Create(stdLog)
381 if err != nil {
382 mpctx.Logger.Fatalf("Error creating std.log: %v", err)
383 }
384 defer f.Close()
385
386 log := logger.New(f)
387 defer log.Cleanup()
388 log.SetOutput(filepath.Join(logsDir, "soong.log"))
389
390 action := &status.Action{
391 Description: product,
392 Outputs: []string{product},
393 }
394 mpctx.Status.StartAction(action)
395 defer logger.Recover(func(err error) {
396 mpctx.Status.FinishAction(status.ActionResult{
397 Action: action,
398 Error: err,
399 Output: errMsgFromLog(stdLog),
400 })
401 })
402
403 ctx := build.Context{ContextImpl: &build.ContextImpl{
404 Context: mpctx.Context,
405 Logger: log,
406 Tracer: mpctx.Tracer,
Colin Cross097ed2a2019-06-08 21:48:58 -0700407 Writer: f,
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700408 Thread: mpctx.Tracer.NewThread(product),
409 Status: &status.Status{},
410 }}
Colin Crossc0b9f6b2019-09-23 12:44:54 -0700411 ctx.Status.AddOutput(terminal.NewStatusOutput(ctx.Writer, "", false,
Sasha Smundakc0c9ef92019-01-23 09:52:57 -0800412 build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700413
Colin Cross00a8a3f2020-10-29 14:08:31 -0700414 args := append([]string(nil), flag.Args()...)
415 args = append(args, "--skip-soong-tests")
416 config := build.NewConfig(ctx, args...)
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700417 config.Environment().Set("OUT_DIR", outDir)
Dan Willemsenf99915f2018-10-25 22:04:42 -0700418 if !*keepArtifacts {
419 config.Environment().Set("EMPTY_NINJA_FILE", "true")
420 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700421 build.FindSources(ctx, config, mpctx.Finder)
422 config.Lunch(ctx, product, *buildVariant)
423
424 defer func() {
425 if *keepArtifacts {
426 args := zip.ZipArgs{
427 FileArgs: []zip.FileArg{
428 {
429 GlobDir: outDir,
430 SourcePrefixToStrip: outDir,
431 },
432 },
433 OutputFilePath: filepath.Join(mpctx.Config.OutDir(), product+".zip"),
434 NumParallelJobs: runtime.NumCPU(),
435 CompressionLevel: 5,
436 }
Colin Cross05518bc2018-09-27 15:06:19 -0700437 if err := zip.Zip(args); err != nil {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700438 log.Fatalf("Error zipping artifacts: %v", err)
439 }
440 }
Dan Willemsenf99915f2018-10-25 22:04:42 -0700441 if !*incremental {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700442 os.RemoveAll(outDir)
443 }
444 }()
445
446 buildWhat := build.BuildProductConfig
447 if !*onlyConfig {
448 buildWhat |= build.BuildSoong
449 if !*onlySoong {
450 buildWhat |= build.BuildKati
451 }
452 }
453
454 before := time.Now()
455 build.Build(ctx, config, buildWhat)
456
457 // Save std_full.log if Kati re-read the makefiles
458 if buildWhat&build.BuildKati != 0 {
Dan Willemsen29971232018-09-26 14:58:30 -0700459 if after, err := os.Stat(config.KatiBuildNinjaFile()); err == nil && after.ModTime().After(before) {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700460 err := copyFile(stdLog, filepath.Join(filepath.Dir(stdLog), "std_full.log"))
461 if err != nil {
462 log.Fatalf("Error copying log file: %s", err)
463 }
464 }
465 }
466
467 mpctx.Status.FinishAction(status.ActionResult{
468 Action: action,
469 })
470}
471
Dan Willemsen34218ec2018-07-19 22:57:18 -0700472type failureCount int
473
474func (f *failureCount) StartAction(action *status.Action, counts status.Counts) {}
475
476func (f *failureCount) FinishAction(result status.ActionResult, counts status.Counts) {
477 if result.Error != nil {
478 *f += 1
479 }
480}
481
482func (f *failureCount) Message(level status.MsgLevel, message string) {
483 if level >= status.ErrorLvl {
484 *f += 1
485 }
486}
487
488func (f *failureCount) Flush() {}
Colin Crosse0df1a32019-06-09 19:40:08 -0700489
490func (f *failureCount) Write(p []byte) (int, error) {
491 // discard writes
492 return len(p), nil
493}
Dan Willemsen9609ad92019-12-05 15:22:41 -0800494
495func splitList(list []string, shardCount int) (ret [][]string) {
496 each := len(list) / shardCount
497 extra := len(list) % shardCount
498 for i := 0; i < shardCount; i++ {
499 count := each
500 if extra > 0 {
501 count += 1
502 extra -= 1
503 }
504 ret = append(ret, list[:count])
505 list = list[count:]
506 }
507 return
508}