open-nomad/client/util.go
Alex Dadgar bddedd7aba Don't deepcopy job when retrieving copy of Alloc
This PR removes deepcopying of the job attached to the allocation in the
alloc runner. This operation is called very often so removing reflect
from the code path and the potentially large number of mallocs need to
create a job reduced memory and cpu pressure.
2017-05-01 14:50:34 -07:00

123 lines
3.3 KiB
Go

package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/ugorji/go/codec"
)
type allocTuple struct {
exist, updated *structs.Allocation
}
// diffResult is used to return the sets that result from a diff
type diffResult struct {
added []*structs.Allocation
removed []*structs.Allocation
updated []allocTuple
ignore []*structs.Allocation
}
func (d *diffResult) GoString() string {
return fmt.Sprintf("allocs: (added %d) (removed %d) (updated %d) (ignore %d)",
len(d.added), len(d.removed), len(d.updated), len(d.ignore))
}
// diffAllocs is used to diff the existing and updated allocations
// to see what has happened.
func diffAllocs(existing []*structs.Allocation, allocs *allocUpdates) *diffResult {
// Scan the existing allocations
result := &diffResult{}
existIdx := make(map[string]struct{})
for _, exist := range existing {
// Mark this as existing
existIdx[exist.ID] = struct{}{}
// Check if the alloc was updated or filtered because an update wasn't
// needed.
alloc, pulled := allocs.pulled[exist.ID]
_, filtered := allocs.filtered[exist.ID]
// If not updated or filtered, removed
if !pulled && !filtered {
result.removed = append(result.removed, exist)
continue
}
// Check for an update
if pulled && alloc.AllocModifyIndex > exist.AllocModifyIndex {
result.updated = append(result.updated, allocTuple{exist, alloc})
continue
}
// Ignore this
result.ignore = append(result.ignore, exist)
}
// Scan the updated allocations for any that are new
for id, pulled := range allocs.pulled {
if _, ok := existIdx[id]; !ok {
result.added = append(result.added, pulled)
}
}
return result
}
// shuffleStrings randomly shuffles the list of strings
func shuffleStrings(list []string) {
for i := range list {
j := rand.Intn(i + 1)
list[i], list[j] = list[j], list[i]
}
}
// persistState is used to help with saving state
func persistState(path string, data interface{}) error {
var buf bytes.Buffer
enc := codec.NewEncoder(&buf, structs.JsonHandlePretty)
if err := enc.Encode(data); err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return fmt.Errorf("failed to make dirs for %s: %v", path, err)
}
tmpPath := path + ".tmp"
if err := ioutil.WriteFile(tmpPath, buf.Bytes(), 0600); err != nil {
return fmt.Errorf("failed to save state to tmp: %v", err)
}
if err := os.Rename(tmpPath, path); err != nil {
return fmt.Errorf("failed to rename tmp to path: %v", err)
}
// Sanity check since users have reported empty state files on disk
if stat, err := os.Stat(path); err != nil {
return fmt.Errorf("unable to stat state file %s: %v", path, err)
} else if stat.Size() == 0 {
return fmt.Errorf("persisted invalid state file %s; see https://github.com/hashicorp/nomad/issues/1367", path)
}
return nil
}
// restoreState is used to read back in the persisted state
func restoreState(path string, data interface{}) error {
buf, err := ioutil.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("failed to read state: %v", err)
}
if err := json.Unmarshal(buf, data); err != nil {
return fmt.Errorf("failed to decode state: %v", err)
}
return nil
}