mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00

Currently, total duration of snapshot saving is exported for prometheus. For more detailed analysis, this commit let etcd export durations of marshalling for prometheus.
189 lines
4.6 KiB
Go
189 lines
4.6 KiB
Go
// Copyright 2015 CoreOS, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package snap
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"hash/crc32"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/coreos/etcd/pkg/pbutil"
|
|
"github.com/coreos/etcd/raft"
|
|
"github.com/coreos/etcd/raft/raftpb"
|
|
"github.com/coreos/etcd/snap/snappb"
|
|
|
|
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/pkg/capnslog"
|
|
)
|
|
|
|
const (
|
|
snapSuffix = ".snap"
|
|
)
|
|
|
|
var (
|
|
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "snap")
|
|
|
|
ErrNoSnapshot = errors.New("snap: no available snapshot")
|
|
ErrEmptySnapshot = errors.New("snap: empty snapshot")
|
|
ErrCRCMismatch = errors.New("snap: crc mismatch")
|
|
crcTable = crc32.MakeTable(crc32.Castagnoli)
|
|
)
|
|
|
|
type Snapshotter struct {
|
|
dir string
|
|
}
|
|
|
|
func New(dir string) *Snapshotter {
|
|
return &Snapshotter{
|
|
dir: dir,
|
|
}
|
|
}
|
|
|
|
func (s *Snapshotter) SaveSnap(snapshot raftpb.Snapshot) error {
|
|
if raft.IsEmptySnap(snapshot) {
|
|
return nil
|
|
}
|
|
return s.save(&snapshot)
|
|
}
|
|
|
|
func (s *Snapshotter) save(snapshot *raftpb.Snapshot) error {
|
|
start := time.Now()
|
|
|
|
fname := fmt.Sprintf("%016x-%016x%s", snapshot.Metadata.Term, snapshot.Metadata.Index, snapSuffix)
|
|
b := pbutil.MustMarshal(snapshot)
|
|
crc := crc32.Update(0, crcTable, b)
|
|
snap := snappb.Snapshot{Crc: crc, Data: b}
|
|
d, err := snap.Marshal()
|
|
if err != nil {
|
|
return err
|
|
} else {
|
|
marshallingDurations.Observe(float64(time.Since(start).Nanoseconds() / int64(time.Microsecond)))
|
|
}
|
|
|
|
err = ioutil.WriteFile(path.Join(s.dir, fname), d, 0666)
|
|
if err == nil {
|
|
saveDurations.Observe(float64(time.Since(start).Nanoseconds() / int64(time.Microsecond)))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (s *Snapshotter) Load() (*raftpb.Snapshot, error) {
|
|
names, err := s.snapNames()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var snap *raftpb.Snapshot
|
|
for _, name := range names {
|
|
if snap, err = loadSnap(s.dir, name); err == nil {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, ErrNoSnapshot
|
|
}
|
|
return snap, nil
|
|
}
|
|
|
|
func loadSnap(dir, name string) (*raftpb.Snapshot, error) {
|
|
fpath := path.Join(dir, name)
|
|
snap, err := Read(fpath)
|
|
if err != nil {
|
|
renameBroken(fpath)
|
|
}
|
|
return snap, err
|
|
}
|
|
|
|
// Read reads the snapshot named by snapname and returns the snapshot.
|
|
func Read(snapname string) (*raftpb.Snapshot, error) {
|
|
b, err := ioutil.ReadFile(snapname)
|
|
if err != nil {
|
|
plog.Errorf("cannot read file %v: %v", snapname, err)
|
|
return nil, err
|
|
}
|
|
|
|
if len(b) == 0 {
|
|
plog.Errorf("unexpected empty snapshot")
|
|
return nil, ErrEmptySnapshot
|
|
}
|
|
|
|
var serializedSnap snappb.Snapshot
|
|
if err = serializedSnap.Unmarshal(b); err != nil {
|
|
plog.Errorf("corrupted snapshot file %v: %v", snapname, err)
|
|
return nil, err
|
|
}
|
|
|
|
if len(serializedSnap.Data) == 0 || serializedSnap.Crc == 0 {
|
|
plog.Errorf("unexpected empty snapshot")
|
|
return nil, ErrEmptySnapshot
|
|
}
|
|
|
|
crc := crc32.Update(0, crcTable, serializedSnap.Data)
|
|
if crc != serializedSnap.Crc {
|
|
plog.Errorf("corrupted snapshot file %v: crc mismatch", snapname)
|
|
return nil, ErrCRCMismatch
|
|
}
|
|
|
|
var snap raftpb.Snapshot
|
|
if err = snap.Unmarshal(serializedSnap.Data); err != nil {
|
|
plog.Errorf("corrupted snapshot file %v: %v", snapname, err)
|
|
return nil, err
|
|
}
|
|
return &snap, nil
|
|
}
|
|
|
|
// snapNames returns the filename of the snapshots in logical time order (from newest to oldest).
|
|
// If there is no available snapshots, an ErrNoSnapshot will be returned.
|
|
func (s *Snapshotter) snapNames() ([]string, error) {
|
|
dir, err := os.Open(s.dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer dir.Close()
|
|
names, err := dir.Readdirnames(-1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
snaps := checkSuffix(names)
|
|
if len(snaps) == 0 {
|
|
return nil, ErrNoSnapshot
|
|
}
|
|
sort.Sort(sort.Reverse(sort.StringSlice(snaps)))
|
|
return snaps, nil
|
|
}
|
|
|
|
func checkSuffix(names []string) []string {
|
|
snaps := []string{}
|
|
for i := range names {
|
|
if strings.HasSuffix(names[i], snapSuffix) {
|
|
snaps = append(snaps, names[i])
|
|
} else {
|
|
plog.Warningf("skipped unexpected non snapshot file %v", names[i])
|
|
}
|
|
}
|
|
return snaps
|
|
}
|
|
|
|
func renameBroken(path string) {
|
|
brokenPath := path + ".broken"
|
|
if err := os.Rename(path, brokenPath); err != nil {
|
|
plog.Warningf("cannot rename broken snapshot file %v to %v: %v", path, brokenPath, err)
|
|
}
|
|
}
|