benchmarks/benchmark/tools/model-load-benchmark/config/config.go (196 lines of code) (raw):
package config
import (
"fmt"
"os"
"strconv"
"strings"
"gopkg.in/yaml.v2"
)
// Config for a deployment
type Config struct {
BasePodSpec string `yaml:"basePodSpec"`
SideCarResources *SideCarResources `yaml:"sideCarResources"`
VolumeAttributes *VolumeAttributes `yaml:"volumeAttributes"`
}
// SideCarResources contains GCSFuse sidecar resources
type SideCarResources struct {
CPULimit Resource `yaml:"cpu-limit"`
MemoryLimit Resource `yaml:"memory-limit"`
EphemeralStorageLimit Resource `yaml:"ephemeral-storage-limit"`
CPURequest Resource `yaml:"cpu-request"`
MemoryRequest Resource `yaml:"memory-request"`
EphemeralStorageRequest Resource `yaml:"ephemeral-storage-request"`
}
// VolumeAttributes contains fuse configurations through mount options and attributes
type VolumeAttributes struct {
BucketName string `yaml:"bucketName"`
MountOptions MountOptions `yaml:"mountOptions"`
FileCacheCapacity Resource `yaml:"fileCacheCapacity"`
FileCacheForRangeRead bool `yaml:"fileCacheForRangeRead"`
MetadataStatCacheCapacity Resource `yaml:"metadataStatCacheCapacity"`
MetadataTypeCacheCapacity Resource `yaml:"metadataTypeCacheCapacity"`
MetadataCacheTTLSeconds Resource `yaml:"metadataCacheTTLSeconds"`
}
// MountOptions contains file cache arguments and other fuse cli args
type MountOptions struct {
ImplicitDirs bool `yaml:"implicit-dirs"`
OnlyDir string `yaml:"only-dir"`
FileCache FileCache `yaml:"file-cache"`
}
// FileCache contains file cache related fuse cli options
type FileCache struct {
EnableParallelDownloads bool `yaml:"enable-parallel-downloads"`
ParallelDownloadsPerFile Resource `yaml:"parallel-downloads-per-file"`
MaxParallelDownloads Resource `yaml:"max-parallel-downloads"`
DownloadChunkSizeMB Resource `yaml:"download-chunk-size-mb"`
}
type Resource struct {
Base int `yaml:"base"`
Step int `yaml:"step,omitempty"`
Max int `yaml:"max"`
Unit string `yaml:"unit,omitempty"`
}
// UnmarshalYAML custom unmarshal for Resource
func (r *Resource) UnmarshalYAML(unmarshal func(interface{}) error) error {
rawData := make(map[string]interface{})
if err := unmarshal(&rawData); err != nil {
return err
}
if baseVal, ok := rawData["base"].(string); ok {
base, unit := parseValueUnit(baseVal)
r.Base = base
r.Unit = unit
} else if baseVal, ok := rawData["base"].(int); ok {
r.Base = baseVal
}
if stepVal, ok := rawData["step"].(int); ok {
r.Step = stepVal
}
if maxVal, ok := rawData["max"].(string); ok {
max, maxUnit := parseValueUnit(maxVal)
if maxUnit != "" && maxUnit != r.Unit {
return fmt.Errorf("unit mismatch between base and max: base unit is %s, but max unit is %s", r.Unit, maxUnit)
}
r.Max = max
} else if maxVal, ok := rawData["max"].(int); ok {
r.Max = maxVal
}
r.setDefaults()
return nil
}
func (r *Resource) setDefaults() {
if r.Step == 0 {
r.Step = 1
}
if r.Max == 0 {
r.Max = r.Base
}
}
// UnmarshalYAML custom unmarshal for MountOptions
func (mo *MountOptions) UnmarshalYAML(unmarshal func(interface{}) error) error {
data := make(map[string]interface{})
err := unmarshal(&data)
if err != nil {
return err
}
for key, value := range data {
switch key {
case "implicit-dirs":
mo.ImplicitDirs, _ = value.(bool)
case "only-dir":
mo.OnlyDir, _ = value.(string)
case "file-cache":
cacheData, err := yaml.Marshal(value)
if err != nil {
return fmt.Errorf("error marshalling file-cache data: %v", err)
}
err = yaml.Unmarshal(cacheData, &mo.FileCache)
if err != nil {
return fmt.Errorf("error unmarshalling file-cache: %v", err)
}
default:
fmt.Printf("Warning: unrecognized key in mountOptions: %s\n", key)
}
}
return nil
}
// LoadConfig loads the configuration from a YAML file
func LoadConfig(filename string) (*Config, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
config := &Config{}
err = yaml.Unmarshal(data, config)
if err != nil {
return nil, err
}
if config.BasePodSpec == "" {
return nil, fmt.Errorf("missing or empty required field 'BasePodSpec'")
}
// Validate that VolumeAttributes and bucketName are set
if config.VolumeAttributes == nil || config.VolumeAttributes.BucketName == "" {
return nil, fmt.Errorf("missing or empty required field 'volumeAttributes.bucketName'")
}
// Validate that only-dir is set
if config.VolumeAttributes.MountOptions.OnlyDir == "" {
return nil, fmt.Errorf("invalid value for 'mountOptions.only-dir': must be set and cannot be '0'")
}
return config, nil
}
// PrettyPrint in formatted YAML string
func (c *Config) PrettyPrint() (string, error) {
yamlData, err := yaml.Marshal(c)
if err != nil {
return "", fmt.Errorf("failed to marshal config to YAML: %v", err)
}
return string(yamlData), nil
}
// ToMountOptionsString outputs formatted string for Mount options attribute under volume mount
func (m *MountOptions) ToMountOptionsString() string {
options := []string{}
if m.ImplicitDirs {
options = append(options, "implicit-dirs")
}
if m.OnlyDir != "" {
options = append(options, fmt.Sprintf("only-dir=%s", m.OnlyDir))
}
options = append(options,
fmt.Sprintf("file-cache:enable-parallel-downloads:%v", m.FileCache.EnableParallelDownloads),
fmt.Sprintf("file-cache:download-chunk-size-mb:%v", m.FileCache.DownloadChunkSizeMB.Base),
)
if m.FileCache.EnableParallelDownloads {
options = append(options,
fmt.Sprintf("file-cache:parallel-downloads-per-file:%v", m.FileCache.ParallelDownloadsPerFile.Base),
fmt.Sprintf("file-cache:max-parallel-downloads:%v", m.FileCache.MaxParallelDownloads.Base))
}
return strings.Join(options, ",")
}
func (v *VolumeAttributes) ToMap() map[string]string {
volAttributes := make(map[string]string)
if v.BucketName != "" {
volAttributes["bucketName"] = v.BucketName
}
volAttributes["mountOptions"] = v.MountOptions.ToMountOptionsString()
volAttributes["fileCacheCapacity"] = fmt.Sprintf("%d%s", v.FileCacheCapacity.Base, v.FileCacheCapacity.Unit)
volAttributes["metadataStatCacheCapacity"] = fmt.Sprintf("%d%s", v.MetadataStatCacheCapacity.Base, v.MetadataStatCacheCapacity.Unit)
volAttributes["metadataTypeCacheCapacity"] = fmt.Sprintf("%d%s", v.MetadataTypeCacheCapacity.Base, v.MetadataTypeCacheCapacity.Unit)
volAttributes["metadataCacheTTLSeconds"] = fmt.Sprintf("%d%s", v.MetadataCacheTTLSeconds.Base, v.MetadataCacheTTLSeconds.Unit)
volAttributes["fileCacheForRangeRead"] = strconv.FormatBool(v.FileCacheForRangeRead)
return volAttributes
}
// ToMap convert sidecar resources to annotations parsed by GCSFuse Sidecar
func (s *SideCarResources) ToMap() map[string]string {
annotations := map[string]string{
"gke-gcsfuse/volumes": "true",
}
if s.CPULimit.Base != 0 {
annotations["gke-gcsfuse/cpu-limit"] = fmt.Sprintf("%d%s", s.CPULimit.Base, s.CPULimit.Unit)
}
if s.MemoryLimit.Base != 0 {
annotations["gke-gcsfuse/memory-limit"] = fmt.Sprintf("%d%s", s.MemoryLimit.Base, s.MemoryLimit.Unit)
}
if s.EphemeralStorageLimit.Base != 0 {
annotations["gke-gcsfuse/ephemeral-storage-limit"] = fmt.Sprintf("%d%s", s.EphemeralStorageLimit.Base, s.EphemeralStorageLimit.Unit)
}
if s.CPURequest.Base != 0 {
annotations["gke-gcsfuse/cpu-request"] = fmt.Sprintf("%d%s", s.CPURequest.Base, s.CPURequest.Unit)
}
if s.MemoryRequest.Base != 0 {
annotations["gke-gcsfuse/memory-request"] = fmt.Sprintf("%d%s", s.MemoryRequest.Base, s.MemoryRequest.Unit)
}
if s.EphemeralStorageRequest.Base != 0 {
annotations["gke-gcsfuse/ephemeral-storage-request"] = fmt.Sprintf("%d%s", s.EphemeralStorageRequest.Base, s.EphemeralStorageRequest.Unit)
}
return annotations
}
func (c *Config) GetBasePodSpec() string {
return c.BasePodSpec
}