go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/client/cmd/cas/casimpl/output.go (about)

     1  // Copyright 2021 The LUCI Authors.
     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  
    15  package casimpl
    16  
    17  import (
    18  	"encoding/json"
    19  	"os"
    20  	"sort"
    21  
    22  	"go.chromium.org/luci/common/data/packedintset"
    23  	"go.chromium.org/luci/common/errors"
    24  )
    25  
    26  type StatusCode int
    27  
    28  // possible exit errors
    29  const (
    30  	// Bad command line arguments were provided
    31  	ArgumentsInvalid StatusCode = iota
    32  	// Error authenticating with RBE-CAS server
    33  	AuthenticationError
    34  	// Error instantiating a new RBE client
    35  	ClientError
    36  	// The provided digest is bad and the file/dir cannot be found
    37  	DigestInvalid
    38  	// Disk I/O issues
    39  	IOError
    40  	// Issue with RPC to RBE-CAS server
    41  	RPCError
    42  	// Any uncategorised error
    43  	Unknown
    44  )
    45  
    46  // writeExitResult writes the status msg
    47  func writeExitResult(path string, statusCode StatusCode, digest string) error {
    48  	if path == "" {
    49  		return nil
    50  	}
    51  
    52  	var toString = map[StatusCode]string{
    53  		ArgumentsInvalid:    "arguments_invalid",
    54  		AuthenticationError: "authentication_error",
    55  		ClientError:         "client_error",
    56  		DigestInvalid:       "digest_invalid",
    57  		IOError:             "io_error",
    58  		RPCError:            "rpc_error",
    59  		Unknown:             "unknown",
    60  	}
    61  
    62  	type ErrorDetails struct {
    63  		Digest string `json:"digest,omitempty"`
    64  	}
    65  
    66  	var body struct {
    67  		Result       string       `json:"result"`
    68  		ErrorDetails ErrorDetails `json:"error_details,omitempty"`
    69  	}
    70  
    71  	body.Result = toString[statusCode]
    72  	if digest != "" {
    73  		body.ErrorDetails = ErrorDetails{Digest: digest}
    74  	}
    75  
    76  	out, err := json.Marshal(body)
    77  	if err != nil {
    78  		return errors.Annotate(err, "failed to marshal json").Err()
    79  	}
    80  	if err := os.WriteFile(path, out, 0600); err != nil {
    81  		return errors.Annotate(err, "failed to write json").Err()
    82  	}
    83  	return nil
    84  }
    85  
    86  // writeStats writes cache stats in packed format.
    87  func writeStats(path string, hot, cold []int64) error {
    88  	// Copy before sort.
    89  	cold = append([]int64{}, cold...)
    90  	hot = append([]int64{}, hot...)
    91  
    92  	sort.Slice(cold, func(i, j int) bool { return cold[i] < cold[j] })
    93  	sort.Slice(hot, func(i, j int) bool { return hot[i] < hot[j] })
    94  
    95  	var sizeCold, sizeHot int64
    96  	for _, fileSize := range cold {
    97  		sizeCold += fileSize
    98  	}
    99  	for _, fileSize := range hot {
   100  		sizeHot += fileSize
   101  	}
   102  
   103  	packedCold, err := packedintset.Pack(cold)
   104  	if err != nil {
   105  		return errors.Annotate(err, "failed to pack uploaded items").Err()
   106  	}
   107  
   108  	packedHot, err := packedintset.Pack(hot)
   109  	if err != nil {
   110  		return errors.Annotate(err, "failed to pack not uploaded items").Err()
   111  	}
   112  
   113  	statsJSON, err := json.Marshal(struct {
   114  		ItemsCold []byte `json:"items_cold"`
   115  		SizeCold  int64  `json:"size_cold"`
   116  		ItemsHot  []byte `json:"items_hot"`
   117  		SizeHot   int64  `json:"size_hot"`
   118  		Result    string `json:"result"`
   119  	}{
   120  		ItemsCold: packedCold,
   121  		SizeCold:  sizeCold,
   122  		ItemsHot:  packedHot,
   123  		SizeHot:   sizeHot,
   124  		Result:    "success",
   125  	})
   126  	if err != nil {
   127  		return errors.Annotate(err, "failed to marshal json").Err()
   128  	}
   129  	if err := os.WriteFile(path, statsJSON, 0600); err != nil {
   130  		return errors.Annotate(err, "failed to write json").Err()
   131  	}
   132  
   133  	return nil
   134  }