Commit 6849b381 authored by Rohit Jnagal's avatar Rohit Jnagal

Update cadvisor dependency.

Godep also decided to remove unused appengine dependency.
parent b65c321a
...@@ -241,88 +241,93 @@ ...@@ -241,88 +241,93 @@
}, },
{ {
"ImportPath": "github.com/google/cadvisor/api", "ImportPath": "github.com/google/cadvisor/api",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
},
{
"ImportPath": "github.com/google/cadvisor/cache/memory",
"Comment": "0.15.1",
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/collector", "ImportPath": "github.com/google/cadvisor/collector",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/container", "ImportPath": "github.com/google/cadvisor/container",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/events", "ImportPath": "github.com/google/cadvisor/events",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/fs", "ImportPath": "github.com/google/cadvisor/fs",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/healthz", "ImportPath": "github.com/google/cadvisor/healthz",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/http", "ImportPath": "github.com/google/cadvisor/http",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/info/v1", "ImportPath": "github.com/google/cadvisor/info/v1",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/info/v2", "ImportPath": "github.com/google/cadvisor/info/v2",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/manager", "ImportPath": "github.com/google/cadvisor/manager",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/metrics", "ImportPath": "github.com/google/cadvisor/metrics",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/pages", "ImportPath": "github.com/google/cadvisor/pages",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/storage", "ImportPath": "github.com/google/cadvisor/storage",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/summary", "ImportPath": "github.com/google/cadvisor/summary",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/utils", "ImportPath": "github.com/google/cadvisor/utils",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/validate", "ImportPath": "github.com/google/cadvisor/validate",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/version", "ImportPath": "github.com/google/cadvisor/version",
"Comment": "0.14.0-9-ga96f2f9", "Comment": "0.15.1",
"Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" "Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
}, },
{ {
"ImportPath": "github.com/google/go-github/github", "ImportPath": "github.com/google/go-github/github",
...@@ -535,10 +540,6 @@ ...@@ -535,10 +540,6 @@
"Rev": "b5adcc2dcdf009d0391547edc6ecbaff889f5bb9" "Rev": "b5adcc2dcdf009d0391547edc6ecbaff889f5bb9"
}, },
{ {
"ImportPath": "google.golang.org/appengine",
"Rev": "6aa67407028217c352e215f5af320a429d0bcf5f"
},
{
"ImportPath": "google.golang.org/cloud/compute/metadata", "ImportPath": "google.golang.org/cloud/compute/metadata",
"Rev": "2e43671e4ad874a7bca65746ff3edb38e6e93762" "Rev": "2e43671e4ad874a7bca65746ff3edb38e6e93762"
}, },
......
...@@ -426,8 +426,7 @@ func convertStats(cont *info.ContainerInfo) []v2.ContainerStats { ...@@ -426,8 +426,7 @@ func convertStats(cont *info.ContainerInfo) []v2.ContainerStats {
stat.Memory = val.Memory stat.Memory = val.Memory
} }
if stat.HasNetwork { if stat.HasNetwork {
// TODO(rjnagal): Return stats about all network interfaces. stat.Network.Interfaces = val.Network.Interfaces
stat.Network = append(stat.Network, val.Network)
} }
if stat.HasFilesystem { if stat.HasFilesystem {
stat.Filesystem = val.Filesystem stat.Filesystem = val.Filesystem
......
...@@ -25,16 +25,16 @@ import ( ...@@ -25,16 +25,16 @@ import (
"github.com/google/cadvisor/utils" "github.com/google/cadvisor/utils"
) )
// TODO(vmarmol): See about refactoring this class, we have an unecessary redirection of containerStorage and InMemoryStorage. // TODO(vmarmol): See about refactoring this class, we have an unecessary redirection of containerCache and InMemoryCache.
// containerStorage is used to store per-container information // containerCache is used to store per-container information
type containerStorage struct { type containerCache struct {
ref info.ContainerReference ref info.ContainerReference
recentStats *utils.TimedStore recentStats *utils.TimedStore
maxAge time.Duration maxAge time.Duration
lock sync.RWMutex lock sync.RWMutex
} }
func (self *containerStorage) AddStats(stats *info.ContainerStats) error { func (self *containerCache) AddStats(stats *info.ContainerStats) error {
self.lock.Lock() self.lock.Lock()
defer self.lock.Unlock() defer self.lock.Unlock()
...@@ -43,7 +43,7 @@ func (self *containerStorage) AddStats(stats *info.ContainerStats) error { ...@@ -43,7 +43,7 @@ func (self *containerStorage) AddStats(stats *info.ContainerStats) error {
return nil return nil
} }
func (self *containerStorage) RecentStats(start, end time.Time, maxStats int) ([]*info.ContainerStats, error) { func (self *containerCache) RecentStats(start, end time.Time, maxStats int) ([]*info.ContainerStats, error) {
self.lock.RLock() self.lock.RLock()
defer self.lock.RUnlock() defer self.lock.RUnlock()
result := self.recentStats.InTimeRange(start, end, maxStats) result := self.recentStats.InTimeRange(start, end, maxStats)
...@@ -54,31 +54,31 @@ func (self *containerStorage) RecentStats(start, end time.Time, maxStats int) ([ ...@@ -54,31 +54,31 @@ func (self *containerStorage) RecentStats(start, end time.Time, maxStats int) ([
return converted, nil return converted, nil
} }
func newContainerStore(ref info.ContainerReference, maxAge time.Duration) *containerStorage { func newContainerStore(ref info.ContainerReference, maxAge time.Duration) *containerCache {
return &containerStorage{ return &containerCache{
ref: ref, ref: ref,
recentStats: utils.NewTimedStore(maxAge, -1), recentStats: utils.NewTimedStore(maxAge, -1),
maxAge: maxAge, maxAge: maxAge,
} }
} }
type InMemoryStorage struct { type InMemoryCache struct {
lock sync.RWMutex lock sync.RWMutex
containerStorageMap map[string]*containerStorage containerCacheMap map[string]*containerCache
maxAge time.Duration maxAge time.Duration
backend storage.StorageDriver backend storage.StorageDriver
} }
func (self *InMemoryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { func (self *InMemoryCache) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
var cstore *containerStorage var cstore *containerCache
var ok bool var ok bool
func() { func() {
self.lock.Lock() self.lock.Lock()
defer self.lock.Unlock() defer self.lock.Unlock()
if cstore, ok = self.containerStorageMap[ref.Name]; !ok { if cstore, ok = self.containerCacheMap[ref.Name]; !ok {
cstore = newContainerStore(ref, self.maxAge) cstore = newContainerStore(ref, self.maxAge)
self.containerStorageMap[ref.Name] = cstore self.containerCacheMap[ref.Name] = cstore
} }
}() }()
...@@ -93,13 +93,13 @@ func (self *InMemoryStorage) AddStats(ref info.ContainerReference, stats *info.C ...@@ -93,13 +93,13 @@ func (self *InMemoryStorage) AddStats(ref info.ContainerReference, stats *info.C
return cstore.AddStats(stats) return cstore.AddStats(stats)
} }
func (self *InMemoryStorage) RecentStats(name string, start, end time.Time, maxStats int) ([]*info.ContainerStats, error) { func (self *InMemoryCache) RecentStats(name string, start, end time.Time, maxStats int) ([]*info.ContainerStats, error) {
var cstore *containerStorage var cstore *containerCache
var ok bool var ok bool
err := func() error { err := func() error {
self.lock.RLock() self.lock.RLock()
defer self.lock.RUnlock() defer self.lock.RUnlock()
if cstore, ok = self.containerStorageMap[name]; !ok { if cstore, ok = self.containerCacheMap[name]; !ok {
return fmt.Errorf("unable to find data for container %v", name) return fmt.Errorf("unable to find data for container %v", name)
} }
return nil return nil
...@@ -111,9 +111,16 @@ func (self *InMemoryStorage) RecentStats(name string, start, end time.Time, maxS ...@@ -111,9 +111,16 @@ func (self *InMemoryStorage) RecentStats(name string, start, end time.Time, maxS
return cstore.RecentStats(start, end, maxStats) return cstore.RecentStats(start, end, maxStats)
} }
func (self *InMemoryStorage) Close() error { func (self *InMemoryCache) Close() error {
self.lock.Lock() self.lock.Lock()
self.containerStorageMap = make(map[string]*containerStorage, 32) self.containerCacheMap = make(map[string]*containerCache, 32)
self.lock.Unlock()
return nil
}
func (self *InMemoryCache) RemoveContainer(containerName string) error {
self.lock.Lock()
delete(self.containerCacheMap, containerName)
self.lock.Unlock() self.lock.Unlock()
return nil return nil
} }
...@@ -121,11 +128,11 @@ func (self *InMemoryStorage) Close() error { ...@@ -121,11 +128,11 @@ func (self *InMemoryStorage) Close() error {
func New( func New(
maxAge time.Duration, maxAge time.Duration,
backend storage.StorageDriver, backend storage.StorageDriver,
) *InMemoryStorage { ) *InMemoryCache {
ret := &InMemoryStorage{ ret := &InMemoryCache{
containerStorageMap: make(map[string]*containerStorage, 32), containerCacheMap: make(map[string]*containerCache, 32),
maxAge: maxAge, maxAge: maxAge,
backend: backend, backend: backend,
} }
return ret return ret
} }
...@@ -40,58 +40,58 @@ func makeStat(i int) *info.ContainerStats { ...@@ -40,58 +40,58 @@ func makeStat(i int) *info.ContainerStats {
} }
} }
func getRecentStats(t *testing.T, memoryStorage *InMemoryStorage, numStats int) []*info.ContainerStats { func getRecentStats(t *testing.T, memoryCache *InMemoryCache, numStats int) []*info.ContainerStats {
stats, err := memoryStorage.RecentStats(containerName, zero, zero, numStats) stats, err := memoryCache.RecentStats(containerName, zero, zero, numStats)
require.Nil(t, err) require.Nil(t, err)
return stats return stats
} }
func TestAddStats(t *testing.T) { func TestAddStats(t *testing.T) {
memoryStorage := New(60*time.Second, nil) memoryCache := New(60*time.Second, nil)
assert := assert.New(t) assert := assert.New(t)
assert.Nil(memoryStorage.AddStats(containerRef, makeStat(0))) assert.Nil(memoryCache.AddStats(containerRef, makeStat(0)))
assert.Nil(memoryStorage.AddStats(containerRef, makeStat(1))) assert.Nil(memoryCache.AddStats(containerRef, makeStat(1)))
assert.Nil(memoryStorage.AddStats(containerRef, makeStat(2))) assert.Nil(memoryCache.AddStats(containerRef, makeStat(2)))
assert.Nil(memoryStorage.AddStats(containerRef, makeStat(0))) assert.Nil(memoryCache.AddStats(containerRef, makeStat(0)))
containerRef2 := info.ContainerReference{ containerRef2 := info.ContainerReference{
Name: "/container2", Name: "/container2",
} }
assert.Nil(memoryStorage.AddStats(containerRef2, makeStat(0))) assert.Nil(memoryCache.AddStats(containerRef2, makeStat(0)))
assert.Nil(memoryStorage.AddStats(containerRef2, makeStat(1))) assert.Nil(memoryCache.AddStats(containerRef2, makeStat(1)))
} }
func TestRecentStatsNoRecentStats(t *testing.T) { func TestRecentStatsNoRecentStats(t *testing.T) {
memoryStorage := makeWithStats(0) memoryCache := makeWithStats(0)
_, err := memoryStorage.RecentStats(containerName, zero, zero, 60) _, err := memoryCache.RecentStats(containerName, zero, zero, 60)
assert.NotNil(t, err) assert.NotNil(t, err)
} }
// Make an instance of InMemoryStorage with n stats. // Make an instance of InMemoryCache with n stats.
func makeWithStats(n int) *InMemoryStorage { func makeWithStats(n int) *InMemoryCache {
memoryStorage := New(60*time.Second, nil) memoryCache := New(60*time.Second, nil)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
memoryStorage.AddStats(containerRef, makeStat(i)) memoryCache.AddStats(containerRef, makeStat(i))
} }
return memoryStorage return memoryCache
} }
func TestRecentStatsGetZeroStats(t *testing.T) { func TestRecentStatsGetZeroStats(t *testing.T) {
memoryStorage := makeWithStats(10) memoryCache := makeWithStats(10)
assert.Len(t, getRecentStats(t, memoryStorage, 0), 0) assert.Len(t, getRecentStats(t, memoryCache, 0), 0)
} }
func TestRecentStatsGetSomeStats(t *testing.T) { func TestRecentStatsGetSomeStats(t *testing.T) {
memoryStorage := makeWithStats(10) memoryCache := makeWithStats(10)
assert.Len(t, getRecentStats(t, memoryStorage, 5), 5) assert.Len(t, getRecentStats(t, memoryCache, 5), 5)
} }
func TestRecentStatsGetAllStats(t *testing.T) { func TestRecentStatsGetAllStats(t *testing.T) {
memoryStorage := makeWithStats(10) memoryCache := makeWithStats(10)
assert.Len(t, getRecentStats(t, memoryStorage, -1), 10) assert.Len(t, getRecentStats(t, memoryCache, -1), 10)
} }
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
"sync" "sync"
"github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/fsouza/go-dockerclient" "github.com/fsouza/go-dockerclient"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
...@@ -55,16 +54,19 @@ var check = sync.Once{} ...@@ -55,16 +54,19 @@ var check = sync.Once{}
func UseSystemd() bool { func UseSystemd() bool {
check.Do(func() { check.Do(func() {
// run and initialize useSystemd useSystemd = false
useSystemd = systemd.UseSystemd()
if !useSystemd { // Check for system.slice in systemd and cpu cgroup.
// Second attempt at checking for systemd, check for a "name=systemd" cgroup. for _, cgroupType := range []string{"name=systemd", "cpu"} {
mnt, err := cgroups.FindCgroupMountpoint("cpu") mnt, err := cgroups.FindCgroupMountpoint(cgroupType)
if err == nil { if err == nil {
// systemd presence does not mean systemd controls cgroups. // systemd presence does not mean systemd controls cgroups.
// If system.slice cgroup exists, then systemd is taking control. // If system.slice cgroup exists, then systemd is taking control.
// This breaks if user creates system.slice manually :) // This breaks if user creates system.slice manually :)
useSystemd = utils.FileExists(mnt + "/system.slice") if utils.FileExists(path.Join(mnt, "system.slice")) {
useSystemd = true
break
}
} }
} }
}) })
......
...@@ -261,17 +261,10 @@ func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error) { ...@@ -261,17 +261,10 @@ func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error) {
} }
// TODO(rjnagal): Remove the conversion when network stats are read from libcontainer. // TODO(rjnagal): Remove the conversion when network stats are read from libcontainer.
net := stats.Network convertInterfaceStats(&stats.Network.InterfaceStats)
// Ingress for host veth is from the container. for i := range stats.Network.Interfaces {
// Hence tx_bytes stat on the host veth is actually number of bytes received by the container. convertInterfaceStats(&stats.Network.Interfaces[i])
stats.Network.RxBytes = net.TxBytes }
stats.Network.RxPackets = net.TxPackets
stats.Network.RxErrors = net.TxErrors
stats.Network.RxDropped = net.TxDropped
stats.Network.TxBytes = net.RxBytes
stats.Network.TxPackets = net.RxPackets
stats.Network.TxErrors = net.RxErrors
stats.Network.TxDropped = net.RxDropped
// Get filesystem stats. // Get filesystem stats.
err = self.getFsStats(stats) err = self.getFsStats(stats)
...@@ -282,6 +275,21 @@ func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error) { ...@@ -282,6 +275,21 @@ func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error) {
return stats, nil return stats, nil
} }
func convertInterfaceStats(stats *info.InterfaceStats) {
net := stats
// Ingress for host veth is from the container.
// Hence tx_bytes stat on the host veth is actually number of bytes received by the container.
stats.RxBytes = net.TxBytes
stats.RxPackets = net.TxPackets
stats.RxErrors = net.TxErrors
stats.RxDropped = net.TxDropped
stats.TxBytes = net.RxBytes
stats.TxPackets = net.RxPackets
stats.TxErrors = net.RxErrors
stats.TxDropped = net.RxDropped
}
func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
if self.name != "/docker" { if self.name != "/docker" {
return []info.ContainerReference{}, nil return []info.ContainerReference{}, nil
......
...@@ -84,15 +84,20 @@ func GetStats(cgroupManager cgroups.Manager, networkInterfaces []string) (*info. ...@@ -84,15 +84,20 @@ func GetStats(cgroupManager cgroups.Manager, networkInterfaces []string) (*info.
} }
stats := toContainerStats(libcontainerStats) stats := toContainerStats(libcontainerStats)
if len(networkInterfaces) != 0 { // TODO(rjnagal): Use networking stats directly from libcontainer.
// ContainerStats only reports stat for one network device. stats.Network.Interfaces = make([]info.InterfaceStats, len(networkInterfaces))
// TODO(rjnagal): Handle multiple physical network devices. for i := range networkInterfaces {
// TODO(rjnagal): Use networking stats directly from libcontainer. interfaceStats, err := sysinfo.GetNetworkStats(networkInterfaces[i])
stats.Network, err = sysinfo.GetNetworkStats(networkInterfaces[0])
if err != nil { if err != nil {
return stats, err return stats, err
} }
stats.Network.Interfaces[i] = interfaceStats
} }
// For backwards compatability.
if len(networkInterfaces) > 0 {
stats.Network.InterfaceStats = stats.Network.Interfaces[0]
}
return stats, nil return stats, nil
} }
...@@ -213,15 +218,25 @@ func toContainerStats2(s *cgroups.Stats, ret *info.ContainerStats) { ...@@ -213,15 +218,25 @@ func toContainerStats2(s *cgroups.Stats, ret *info.ContainerStats) {
} }
func toContainerStats3(libcontainerStats *libcontainer.Stats, ret *info.ContainerStats) { func toContainerStats3(libcontainerStats *libcontainer.Stats, ret *info.ContainerStats) {
// TODO(vmarmol): Handle multiple interfaces. ret.Network.Interfaces = make([]info.InterfaceStats, len(libcontainerStats.Interfaces))
ret.Network.RxBytes = libcontainerStats.Interfaces[0].RxBytes for i := range libcontainerStats.Interfaces {
ret.Network.RxPackets = libcontainerStats.Interfaces[0].RxPackets ret.Network.Interfaces[i] = info.InterfaceStats{
ret.Network.RxErrors = libcontainerStats.Interfaces[0].RxErrors Name: libcontainerStats.Interfaces[i].Name,
ret.Network.RxDropped = libcontainerStats.Interfaces[0].RxDropped RxBytes: libcontainerStats.Interfaces[i].RxBytes,
ret.Network.TxBytes = libcontainerStats.Interfaces[0].TxBytes RxPackets: libcontainerStats.Interfaces[i].RxPackets,
ret.Network.TxPackets = libcontainerStats.Interfaces[0].TxPackets RxErrors: libcontainerStats.Interfaces[i].RxErrors,
ret.Network.TxErrors = libcontainerStats.Interfaces[0].TxErrors RxDropped: libcontainerStats.Interfaces[i].RxDropped,
ret.Network.TxDropped = libcontainerStats.Interfaces[0].TxDropped TxBytes: libcontainerStats.Interfaces[i].TxBytes,
TxPackets: libcontainerStats.Interfaces[i].TxPackets,
TxErrors: libcontainerStats.Interfaces[i].TxErrors,
TxDropped: libcontainerStats.Interfaces[i].TxDropped,
}
}
// Add to base struct for backwards compatability.
if len(ret.Network.Interfaces) > 0 {
ret.Network.InterfaceStats = ret.Network.Interfaces[0]
}
} }
func toContainerStats(libcontainerStats *libcontainer.Stats) *info.ContainerStats { func toContainerStats(libcontainerStats *libcontainer.Stats) *info.ContainerStats {
......
...@@ -305,15 +305,13 @@ func (self *rawContainerHandler) getFsStats(stats *info.ContainerStats) error { ...@@ -305,15 +305,13 @@ func (self *rawContainerHandler) getFsStats(stats *info.ContainerStats) error {
} }
func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) { func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) {
var networkInterfaces []string
nd, err := self.GetRootNetworkDevices() nd, err := self.GetRootNetworkDevices()
if err != nil { if err != nil {
return new(info.ContainerStats), err return new(info.ContainerStats), err
} }
if len(nd) != 0 { networkInterfaces := make([]string, len(nd))
// ContainerStats only reports stat for one network device. for i := range nd {
// TODO(rjnagal): Handle multiple physical network devices. networkInterfaces[i] = nd[i].Name
networkInterfaces = []string{nd[0].Name}
} }
stats, err := libcontainer.GetStats(self.cgroupManager, networkInterfaces) stats, err := libcontainer.GetStats(self.cgroupManager, networkInterfaces)
if err != nil { if err != nil {
......
...@@ -75,8 +75,13 @@ func NewFsInfo(context Context) (FsInfo, error) { ...@@ -75,8 +75,13 @@ func NewFsInfo(context Context) (FsInfo, error) {
partitions := make(map[string]partition, 0) partitions := make(map[string]partition, 0)
fsInfo := &RealFsInfo{} fsInfo := &RealFsInfo{}
fsInfo.labels = make(map[string]string, 0) fsInfo.labels = make(map[string]string, 0)
supportedFsType := map[string]bool{
// all ext systems are checked through prefix.
"btrfs": true,
"xfs": true,
}
for _, mount := range mounts { for _, mount := range mounts {
if !strings.HasPrefix(mount.Fstype, "ext") && mount.Fstype != "btrfs" { if !strings.HasPrefix(mount.Fstype, "ext") && !supportedFsType[mount.Fstype] {
continue continue
} }
// Avoid bind mounts. // Avoid bind mounts.
......
...@@ -312,7 +312,9 @@ type MemoryStatsMemoryData struct { ...@@ -312,7 +312,9 @@ type MemoryStatsMemoryData struct {
Pgmajfault uint64 `json:"pgmajfault"` Pgmajfault uint64 `json:"pgmajfault"`
} }
type NetworkStats struct { type InterfaceStats struct {
// The name of the interface.
Name string `json:"name"`
// Cumulative count of bytes received. // Cumulative count of bytes received.
RxBytes uint64 `json:"rx_bytes"` RxBytes uint64 `json:"rx_bytes"`
// Cumulative count of packets received. // Cumulative count of packets received.
...@@ -331,6 +333,11 @@ type NetworkStats struct { ...@@ -331,6 +333,11 @@ type NetworkStats struct {
TxDropped uint64 `json:"tx_dropped"` TxDropped uint64 `json:"tx_dropped"`
} }
type NetworkStats struct {
InterfaceStats `json:",inline"`
Interfaces []InterfaceStats `json:"interfaces,omitempty"`
}
type FsStats struct { type FsStats struct {
// The block device name associated with the filesystem. // The block device name associated with the filesystem.
Device string `json:"device,omitempty"` Device string `json:"device,omitempty"`
......
...@@ -72,6 +72,11 @@ type ContainerSpec struct { ...@@ -72,6 +72,11 @@ type ContainerSpec struct {
HasMemory bool `json:"has_memory"` HasMemory bool `json:"has_memory"`
Memory MemorySpec `json:"memory,omitempty"` Memory MemorySpec `json:"memory,omitempty"`
// Following resources have no associated spec, but are being isolated.
HasNetwork bool `json:"has_network"`
HasFilesystem bool `json:"has_filesystem"`
HasDiskIo bool `json:"has_diskio"`
} }
type ContainerStats struct { type ContainerStats struct {
...@@ -87,8 +92,8 @@ type ContainerStats struct { ...@@ -87,8 +92,8 @@ type ContainerStats struct {
HasMemory bool `json:"has_memory"` HasMemory bool `json:"has_memory"`
Memory v1.MemoryStats `json:"memory,omitempty"` Memory v1.MemoryStats `json:"memory,omitempty"`
// Network statistics // Network statistics
HasNetwork bool `json:"has_network"` HasNetwork bool `json:"has_network"`
Network []v1.NetworkStats `json:"network,omitempty"` Network NetworkStats `json:"network,omitempty"`
// Filesystem statistics // Filesystem statistics
HasFilesystem bool `json:"has_filesystem"` HasFilesystem bool `json:"has_filesystem"`
Filesystem []v1.FsStats `json:"filesystem,omitempty"` Filesystem []v1.FsStats `json:"filesystem,omitempty"`
...@@ -184,3 +189,8 @@ type ProcessInfo struct { ...@@ -184,3 +189,8 @@ type ProcessInfo struct {
CgroupPath string `json:"cgroup_path"` CgroupPath string `json:"cgroup_path"`
Cmd string `json:"cmd"` Cmd string `json:"cmd"`
} }
type NetworkStats struct {
// Network stats by interface.
Interfaces []v1.InterfaceStats `json:"interfaces,omitempty"`
}
...@@ -28,11 +28,11 @@ import ( ...@@ -28,11 +28,11 @@ import (
"github.com/docker/docker/pkg/units" "github.com/docker/docker/pkg/units"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/google/cadvisor/cache/memory"
"github.com/google/cadvisor/collector" "github.com/google/cadvisor/collector"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/info/v2"
"github.com/google/cadvisor/storage/memory"
"github.com/google/cadvisor/summary" "github.com/google/cadvisor/summary"
"github.com/google/cadvisor/utils/cpuload" "github.com/google/cadvisor/utils/cpuload"
) )
...@@ -56,7 +56,7 @@ type containerInfo struct { ...@@ -56,7 +56,7 @@ type containerInfo struct {
type containerData struct { type containerData struct {
handler container.ContainerHandler handler container.ContainerHandler
info containerInfo info containerInfo
memoryStorage *memory.InMemoryStorage memoryCache *memory.InMemoryCache
lock sync.Mutex lock sync.Mutex
loadReader cpuload.CpuLoadReader loadReader cpuload.CpuLoadReader
summaryReader *summary.StatsSummary summaryReader *summary.StatsSummary
...@@ -81,6 +81,10 @@ func (c *containerData) Start() error { ...@@ -81,6 +81,10 @@ func (c *containerData) Start() error {
} }
func (c *containerData) Stop() error { func (c *containerData) Stop() error {
err := c.memoryCache.RemoveContainer(c.info.Name)
if err != nil {
return err
}
c.stop <- true c.stop <- true
return nil return nil
} }
...@@ -132,26 +136,22 @@ func (c *containerData) getCgroupPath(cgroups string) (string, error) { ...@@ -132,26 +136,22 @@ func (c *containerData) getCgroupPath(cgroups string) (string, error) {
return string(matches[1]), nil return string(matches[1]), nil
} }
func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { func (c *containerData) GetProcessList(cadvisorContainer string, inHostNamespace bool) ([]v2.ProcessInfo, error) {
// report all processes for root. // report all processes for root.
isRoot := c.info.Name == "/" isRoot := c.info.Name == "/"
pidMap := map[int]bool{}
if !isRoot {
pids, err := c.handler.ListProcesses(container.ListSelf)
if err != nil {
return nil, err
}
for _, pid := range pids {
pidMap[pid] = true
}
}
// TODO(rjnagal): Take format as an option? // TODO(rjnagal): Take format as an option?
format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm,cgroup" format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm,cgroup"
args := []string{"-e", "-o", format} args := []string{}
command := "ps"
if !inHostNamespace {
command = "/usr/sbin/chroot"
args = append(args, "/rootfs", "ps")
}
args = append(args, "-e", "-o", format)
expectedFields := 12 expectedFields := 12
out, err := exec.Command("ps", args...).Output() out, err := exec.Command(command, args...).Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute ps command: %v", err) return nil, fmt.Errorf("failed to execute %q command: %v", command, err)
} }
processes := []v2.ProcessInfo{} processes := []v2.ProcessInfo{}
lines := strings.Split(string(out), "\n") lines := strings.Split(string(out), "\n")
...@@ -187,15 +187,21 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { ...@@ -187,15 +187,21 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid virtual size %q: %v", fields[7], err) return nil, fmt.Errorf("invalid virtual size %q: %v", fields[7], err)
} }
cgroup, err := c.getCgroupPath(fields[11])
if err != nil {
return nil, fmt.Errorf("could not parse cgroup path from %q: %v", fields[10], err)
}
// Remove the ps command we just ran from cadvisor container.
// Not necessary, but makes the cadvisor page look cleaner.
if !inHostNamespace && cadvisorContainer == cgroup && fields[10] == "ps" {
continue
}
var cgroupPath string var cgroupPath string
if isRoot { if isRoot {
cgroupPath, err = c.getCgroupPath(fields[11]) cgroupPath = cgroup
if err != nil {
return nil, fmt.Errorf("could not parse cgroup path from %q: %v", fields[10], err)
}
} }
if isRoot || pidMap[pid] == true { if isRoot || c.info.Name == cgroup {
processes = append(processes, v2.ProcessInfo{ processes = append(processes, v2.ProcessInfo{
User: fields[0], User: fields[0],
Pid: pid, Pid: pid,
...@@ -215,8 +221,8 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { ...@@ -215,8 +221,8 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) {
return processes, nil return processes, nil
} }
func newContainerData(containerName string, memoryStorage *memory.InMemoryStorage, handler container.ContainerHandler, loadReader cpuload.CpuLoadReader, logUsage bool, collectorManager collector.CollectorManager) (*containerData, error) { func newContainerData(containerName string, memoryCache *memory.InMemoryCache, handler container.ContainerHandler, loadReader cpuload.CpuLoadReader, logUsage bool, collectorManager collector.CollectorManager) (*containerData, error) {
if memoryStorage == nil { if memoryCache == nil {
return nil, fmt.Errorf("nil memory storage") return nil, fmt.Errorf("nil memory storage")
} }
if handler == nil { if handler == nil {
...@@ -229,7 +235,7 @@ func newContainerData(containerName string, memoryStorage *memory.InMemoryStorag ...@@ -229,7 +235,7 @@ func newContainerData(containerName string, memoryStorage *memory.InMemoryStorag
cont := &containerData{ cont := &containerData{
handler: handler, handler: handler,
memoryStorage: memoryStorage, memoryCache: memoryCache,
housekeepingInterval: *HousekeepingInterval, housekeepingInterval: *HousekeepingInterval,
loadReader: loadReader, loadReader: loadReader,
logUsage: logUsage, logUsage: logUsage,
...@@ -256,7 +262,7 @@ func newContainerData(containerName string, memoryStorage *memory.InMemoryStorag ...@@ -256,7 +262,7 @@ func newContainerData(containerName string, memoryStorage *memory.InMemoryStorag
func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Time { func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Time {
if *allowDynamicHousekeeping { if *allowDynamicHousekeeping {
var empty time.Time var empty time.Time
stats, err := self.memoryStorage.RecentStats(self.info.Name, empty, empty, 2) stats, err := self.memoryCache.RecentStats(self.info.Name, empty, empty, 2)
if err != nil { if err != nil {
if self.allowErrorLogging() { if self.allowErrorLogging() {
glog.Warningf("Failed to get RecentStats(%q) while determining the next housekeeping: %v", self.info.Name, err) glog.Warningf("Failed to get RecentStats(%q) while determining the next housekeeping: %v", self.info.Name, err)
...@@ -311,7 +317,7 @@ func (c *containerData) housekeeping() { ...@@ -311,7 +317,7 @@ func (c *containerData) housekeeping() {
if c.logUsage { if c.logUsage {
const numSamples = 60 const numSamples = 60
var empty time.Time var empty time.Time
stats, err := c.memoryStorage.RecentStats(c.info.Name, empty, empty, numSamples) stats, err := c.memoryCache.RecentStats(c.info.Name, empty, empty, numSamples)
if err != nil { if err != nil {
if c.allowErrorLogging() { if c.allowErrorLogging() {
glog.Infof("[%s] Failed to get recent stats for logging usage: %v", c.info.Name, err) glog.Infof("[%s] Failed to get recent stats for logging usage: %v", c.info.Name, err)
...@@ -434,7 +440,7 @@ func (c *containerData) updateStats() error { ...@@ -434,7 +440,7 @@ func (c *containerData) updateStats() error {
} }
return err return err
} }
err = c.memoryStorage.AddStats(ref, stats) err = c.memoryCache.AddStats(ref, stats)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -22,11 +22,11 @@ import ( ...@@ -22,11 +22,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/google/cadvisor/cache/memory"
"github.com/google/cadvisor/collector" "github.com/google/cadvisor/collector"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
itest "github.com/google/cadvisor/info/v1/test" itest "github.com/google/cadvisor/info/v1/test"
"github.com/google/cadvisor/storage/memory"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -34,25 +34,25 @@ import ( ...@@ -34,25 +34,25 @@ import (
const containerName = "/container" const containerName = "/container"
// Create a containerData instance for a test. // Create a containerData instance for a test.
func setupContainerData(t *testing.T, spec info.ContainerSpec) (*containerData, *container.MockContainerHandler, *memory.InMemoryStorage) { func setupContainerData(t *testing.T, spec info.ContainerSpec) (*containerData, *container.MockContainerHandler, *memory.InMemoryCache) {
mockHandler := container.NewMockContainerHandler(containerName) mockHandler := container.NewMockContainerHandler(containerName)
mockHandler.On("GetSpec").Return( mockHandler.On("GetSpec").Return(
spec, spec,
nil, nil,
) )
memoryStorage := memory.New(60, nil) memoryCache := memory.New(60, nil)
ret, err := newContainerData(containerName, memoryStorage, mockHandler, nil, false, &collector.FakeCollectorManager{}) ret, err := newContainerData(containerName, memoryCache, mockHandler, nil, false, &collector.FakeCollectorManager{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
return ret, mockHandler, memoryStorage return ret, mockHandler, memoryCache
} }
// Create a containerData instance for a test and add a default GetSpec mock. // Create a containerData instance for a test and add a default GetSpec mock.
func newTestContainerData(t *testing.T) (*containerData, *container.MockContainerHandler, *memory.InMemoryStorage) { func newTestContainerData(t *testing.T) (*containerData, *container.MockContainerHandler, *memory.InMemoryCache) {
spec := itest.GenerateRandomContainerSpec(4) spec := itest.GenerateRandomContainerSpec(4)
ret, mockHandler, memoryStorage := setupContainerData(t, spec) ret, mockHandler, memoryCache := setupContainerData(t, spec)
return ret, mockHandler, memoryStorage return ret, mockHandler, memoryCache
} }
func TestUpdateSubcontainers(t *testing.T) { func TestUpdateSubcontainers(t *testing.T) {
...@@ -116,9 +116,9 @@ func TestUpdateSubcontainersWithErrorOnDeadContainer(t *testing.T) { ...@@ -116,9 +116,9 @@ func TestUpdateSubcontainersWithErrorOnDeadContainer(t *testing.T) {
mockHandler.AssertExpectations(t) mockHandler.AssertExpectations(t)
} }
func checkNumStats(t *testing.T, memoryStorage *memory.InMemoryStorage, numStats int) { func checkNumStats(t *testing.T, memoryCache *memory.InMemoryCache, numStats int) {
var empty time.Time var empty time.Time
stats, err := memoryStorage.RecentStats(containerName, empty, empty, -1) stats, err := memoryCache.RecentStats(containerName, empty, empty, -1)
require.Nil(t, err) require.Nil(t, err)
assert.Len(t, stats, numStats) assert.Len(t, stats, numStats)
} }
...@@ -127,7 +127,7 @@ func TestUpdateStats(t *testing.T) { ...@@ -127,7 +127,7 @@ func TestUpdateStats(t *testing.T) {
statsList := itest.GenerateRandomStats(1, 4, 1*time.Second) statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
stats := statsList[0] stats := statsList[0]
cd, mockHandler, memoryStorage := newTestContainerData(t) cd, mockHandler, memoryCache := newTestContainerData(t)
mockHandler.On("GetStats").Return( mockHandler.On("GetStats").Return(
stats, stats,
nil, nil,
...@@ -138,7 +138,7 @@ func TestUpdateStats(t *testing.T) { ...@@ -138,7 +138,7 @@ func TestUpdateStats(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
checkNumStats(t, memoryStorage, 1) checkNumStats(t, memoryCache, 1)
mockHandler.AssertExpectations(t) mockHandler.AssertExpectations(t)
} }
......
...@@ -62,7 +62,12 @@ func getClockSpeed(procInfo []byte) (uint64, error) { ...@@ -62,7 +62,12 @@ func getClockSpeed(procInfo []byte) (uint64, error) {
// Fall back to /proc/cpuinfo // Fall back to /proc/cpuinfo
matches := CpuClockSpeedMHz.FindSubmatch(procInfo) matches := CpuClockSpeedMHz.FindSubmatch(procInfo)
if len(matches) != 2 { if len(matches) != 2 {
return 0, fmt.Errorf("could not detect clock speed from output: %q", string(procInfo)) //Check if we are running on Power systems which have a different format
CpuClockSpeedMHz, _ = regexp.Compile("clock\\t*: +([0-9]+.[0-9]+)MHz")
matches = CpuClockSpeedMHz.FindSubmatch(procInfo)
if len(matches) != 2 {
return 0, fmt.Errorf("could not detect clock speed from output: %q", string(procInfo))
}
} }
speed, err := strconv.ParseFloat(string(matches[1]), 64) speed, err := strconv.ParseFloat(string(matches[1]), 64)
if err != nil { if err != nil {
......
...@@ -18,6 +18,7 @@ package manager ...@@ -18,6 +18,7 @@ package manager
import ( import (
"flag" "flag"
"fmt" "fmt"
"os"
"path" "path"
"regexp" "regexp"
"strconv" "strconv"
...@@ -27,6 +28,7 @@ import ( ...@@ -27,6 +28,7 @@ import (
"github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/google/cadvisor/cache/memory"
"github.com/google/cadvisor/collector" "github.com/google/cadvisor/collector"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/container/docker"
...@@ -35,7 +37,6 @@ import ( ...@@ -35,7 +37,6 @@ import (
"github.com/google/cadvisor/fs" "github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/info/v2"
"github.com/google/cadvisor/storage/memory"
"github.com/google/cadvisor/utils/cpuload" "github.com/google/cadvisor/utils/cpuload"
"github.com/google/cadvisor/utils/oomparser" "github.com/google/cadvisor/utils/oomparser"
"github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/utils/sysfs"
...@@ -113,8 +114,8 @@ type Manager interface { ...@@ -113,8 +114,8 @@ type Manager interface {
} }
// New takes a memory storage and returns a new manager. // New takes a memory storage and returns a new manager.
func New(memoryStorage *memory.InMemoryStorage, sysfs sysfs.SysFs) (Manager, error) { func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs) (Manager, error) {
if memoryStorage == nil { if memoryCache == nil {
return nil, fmt.Errorf("manager requires memory storage") return nil, fmt.Errorf("manager requires memory storage")
} }
...@@ -130,12 +131,20 @@ func New(memoryStorage *memory.InMemoryStorage, sysfs sysfs.SysFs) (Manager, err ...@@ -130,12 +131,20 @@ func New(memoryStorage *memory.InMemoryStorage, sysfs sysfs.SysFs) (Manager, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If cAdvisor was started with host's rootfs mounted, assume that its running
// in its own namespaces.
inHostNamespace := false
if _, err := os.Stat("/rootfs/proc"); os.IsNotExist(err) {
inHostNamespace = true
}
newManager := &manager{ newManager := &manager{
containers: make(map[namespacedContainerName]*containerData), containers: make(map[namespacedContainerName]*containerData),
quitChannels: make([]chan error, 0, 2), quitChannels: make([]chan error, 0, 2),
memoryStorage: memoryStorage, memoryCache: memoryCache,
fsInfo: fsInfo, fsInfo: fsInfo,
cadvisorContainer: selfContainer, cadvisorContainer: selfContainer,
inHostNamespace: inHostNamespace,
startupTime: time.Now(), startupTime: time.Now(),
} }
...@@ -169,12 +178,13 @@ type namespacedContainerName struct { ...@@ -169,12 +178,13 @@ type namespacedContainerName struct {
type manager struct { type manager struct {
containers map[namespacedContainerName]*containerData containers map[namespacedContainerName]*containerData
containersLock sync.RWMutex containersLock sync.RWMutex
memoryStorage *memory.InMemoryStorage memoryCache *memory.InMemoryCache
fsInfo fs.FsInfo fsInfo fs.FsInfo
machineInfo info.MachineInfo machineInfo info.MachineInfo
versionInfo info.VersionInfo versionInfo info.VersionInfo
quitChannels []chan error quitChannels []chan error
cadvisorContainer string cadvisorContainer string
inHostNamespace bool
dockerContainersRegexp *regexp.Regexp dockerContainersRegexp *regexp.Regexp
loadReader cpuload.CpuLoadReader loadReader cpuload.CpuLoadReader
eventHandler events.EventManager eventHandler events.EventManager
...@@ -217,7 +227,7 @@ func (self *manager) Start() error { ...@@ -217,7 +227,7 @@ func (self *manager) Start() error {
// Watch for OOMs. // Watch for OOMs.
err = self.watchForNewOoms() err = self.watchForNewOoms()
if err != nil { if err != nil {
glog.Errorf("Failed to start OOM watcher, will not get OOM events: %v", err) glog.Warningf("Could not configure a source for OOM detection, disabling OOM events: %v", err)
} }
// If there are no factories, don't start any housekeeping and serve the information we do have. // If there are no factories, don't start any housekeeping and serve the information we do have.
...@@ -361,9 +371,12 @@ func (self *manager) GetContainerSpec(containerName string, options v2.RequestOp ...@@ -361,9 +371,12 @@ func (self *manager) GetContainerSpec(containerName string, options v2.RequestOp
func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec { func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec {
specV1 := self.getAdjustedSpec(cinfo) specV1 := self.getAdjustedSpec(cinfo)
specV2 := v2.ContainerSpec{ specV2 := v2.ContainerSpec{
CreationTime: specV1.CreationTime, CreationTime: specV1.CreationTime,
HasCpu: specV1.HasCpu, HasCpu: specV1.HasCpu,
HasMemory: specV1.HasMemory, HasMemory: specV1.HasMemory,
HasFilesystem: specV1.HasFilesystem,
HasNetwork: specV1.HasNetwork,
HasDiskIo: specV1.HasDiskIo,
} }
if specV1.HasCpu { if specV1.HasCpu {
specV2.Cpu.Limit = specV1.Cpu.Limit specV2.Cpu.Limit = specV1.Cpu.Limit
...@@ -409,7 +422,7 @@ func (self *manager) containerDataToContainerInfo(cont *containerData, query *in ...@@ -409,7 +422,7 @@ func (self *manager) containerDataToContainerInfo(cont *containerData, query *in
return nil, err return nil, err
} }
stats, err := self.memoryStorage.RecentStats(cinfo.Name, query.Start, query.End, query.NumStats) stats, err := self.memoryCache.RecentStats(cinfo.Name, query.Start, query.End, query.NumStats)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -594,7 +607,7 @@ func (self *manager) getRequestedContainers(containerName string, options v2.Req ...@@ -594,7 +607,7 @@ func (self *manager) getRequestedContainers(containerName string, options v2.Req
func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) { func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) {
var empty time.Time var empty time.Time
// Get latest data from filesystems hanging off root container. // Get latest data from filesystems hanging off root container.
stats, err := self.memoryStorage.RecentStats("/", empty, empty, 1) stats, err := self.memoryCache.RecentStats("/", empty, empty, 1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -668,7 +681,7 @@ func (m *manager) GetProcessList(containerName string, options v2.RequestOptions ...@@ -668,7 +681,7 @@ func (m *manager) GetProcessList(containerName string, options v2.RequestOptions
// TODO(rjnagal): handle count? Only if we can do count by type (eg. top 5 cpu users) // TODO(rjnagal): handle count? Only if we can do count by type (eg. top 5 cpu users)
ps := []v2.ProcessInfo{} ps := []v2.ProcessInfo{}
for _, cont := range conts { for _, cont := range conts {
ps, err = cont.GetProcessList() ps, err = cont.GetProcessList(m.cadvisorContainer, m.inHostNamespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -693,7 +706,7 @@ func (m *manager) createContainer(containerName string) error { ...@@ -693,7 +706,7 @@ func (m *manager) createContainer(containerName string) error {
return err return err
} }
logUsage := *logCadvisorUsage && containerName == m.cadvisorContainer logUsage := *logCadvisorUsage && containerName == m.cadvisorContainer
cont, err := newContainerData(containerName, m.memoryStorage, handler, m.loadReader, logUsage, collectorManager) cont, err := newContainerData(containerName, m.memoryCache, handler, m.loadReader, logUsage, collectorManager)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -22,19 +22,19 @@ import ( ...@@ -22,19 +22,19 @@ import (
"testing" "testing"
"time" "time"
"github.com/google/cadvisor/cache/memory"
"github.com/google/cadvisor/collector" "github.com/google/cadvisor/collector"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/container/docker"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
itest "github.com/google/cadvisor/info/v1/test" itest "github.com/google/cadvisor/info/v1/test"
"github.com/google/cadvisor/storage/memory"
"github.com/google/cadvisor/utils/sysfs/fakesysfs" "github.com/google/cadvisor/utils/sysfs/fakesysfs"
) )
// TODO(vmarmol): Refactor these tests. // TODO(vmarmol): Refactor these tests.
func createManagerAndAddContainers( func createManagerAndAddContainers(
memoryStorage *memory.InMemoryStorage, memoryCache *memory.InMemoryCache,
sysfs *fakesysfs.FakeSysFs, sysfs *fakesysfs.FakeSysFs,
containers []string, containers []string,
f func(*container.MockContainerHandler), f func(*container.MockContainerHandler),
...@@ -42,9 +42,9 @@ func createManagerAndAddContainers( ...@@ -42,9 +42,9 @@ func createManagerAndAddContainers(
) *manager { ) *manager {
container.ClearContainerHandlerFactories() container.ClearContainerHandlerFactories()
mif := &manager{ mif := &manager{
containers: make(map[namespacedContainerName]*containerData), containers: make(map[namespacedContainerName]*containerData),
quitChannels: make([]chan error, 0, 2), quitChannels: make([]chan error, 0, 2),
memoryStorage: memoryStorage, memoryCache: memoryCache,
} }
for _, name := range containers { for _, name := range containers {
mockHandler := container.NewMockContainerHandler(name) mockHandler := container.NewMockContainerHandler(name)
...@@ -53,7 +53,7 @@ func createManagerAndAddContainers( ...@@ -53,7 +53,7 @@ func createManagerAndAddContainers(
spec, spec,
nil, nil,
).Once() ).Once()
cont, err := newContainerData(name, memoryStorage, mockHandler, nil, false, &collector.FakeCollectorManager{}) cont, err := newContainerData(name, memoryCache, mockHandler, nil, false, &collector.FakeCollectorManager{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -82,10 +82,10 @@ func expectManagerWithContainers(containers []string, query *info.ContainerInfoR ...@@ -82,10 +82,10 @@ func expectManagerWithContainers(containers []string, query *info.ContainerInfoR
infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second) infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second)
} }
memoryStorage := memory.New(time.Duration(query.NumStats)*time.Second, nil) memoryCache := memory.New(time.Duration(query.NumStats)*time.Second, nil)
sysfs := &fakesysfs.FakeSysFs{} sysfs := &fakesysfs.FakeSysFs{}
m := createManagerAndAddContainers( m := createManagerAndAddContainers(
memoryStorage, memoryCache,
sysfs, sysfs,
containers, containers,
func(h *container.MockContainerHandler) { func(h *container.MockContainerHandler) {
...@@ -95,7 +95,7 @@ func expectManagerWithContainers(containers []string, query *info.ContainerInfoR ...@@ -95,7 +95,7 @@ func expectManagerWithContainers(containers []string, query *info.ContainerInfoR
t.Error(err) t.Error(err)
} }
for _, stat := range cinfo.Stats { for _, stat := range cinfo.Stats {
err = memoryStorage.AddStats(ref, stat) err = memoryCache.AddStats(ref, stat)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
......
...@@ -57,14 +57,17 @@ func (p testSubcontainersInfoProvider) SubcontainersInfo(string, *info.Container ...@@ -57,14 +57,17 @@ func (p testSubcontainersInfoProvider) SubcontainersInfo(string, *info.Container
}, },
}, },
Network: info.NetworkStats{ Network: info.NetworkStats{
RxBytes: 14, InterfaceStats: info.InterfaceStats{
RxPackets: 15, Name: "eth0",
RxErrors: 16, RxBytes: 14,
RxDropped: 17, RxPackets: 15,
TxBytes: 18, RxErrors: 16,
TxPackets: 19, RxDropped: 17,
TxErrors: 20, TxBytes: 18,
TxDropped: 21, TxPackets: 19,
TxErrors: 20,
TxDropped: 21,
},
}, },
Filesystem: []info.FsStats{ Filesystem: []info.FsStats{
{ {
......
...@@ -192,6 +192,16 @@ const containersHtmlTemplate = ` ...@@ -192,6 +192,16 @@ const containersHtmlTemplate = `
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">Network</h3> <h3 class="panel-title">Network</h3>
</div> </div>
<div class="panel-body">
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="network-selection-dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span id="network-selection-text"></span>
<span class="caret"></span>
</button>
<ul id="network-selection" class="dropdown-menu" role="menu" aria-labelledby="network-selection-dropdown">
</ul>
</div>
</div>
<div class="panel-body"> <div class="panel-body">
<h4>Throughput</h4> <h4>Throughput</h4>
<div id="network-bytes-chart"></div> <div id="network-bytes-chart"></div>
......
...@@ -375,12 +375,32 @@ function drawMemoryUsage(elementId, machineInfo, containerInfo) { ...@@ -375,12 +375,32 @@ function drawMemoryUsage(elementId, machineInfo, containerInfo) {
drawLineChart(titles, data, elementId, "Megabytes"); drawLineChart(titles, data, elementId, "Megabytes");
} }
// Get the index of the interface with the specified name.
function getNetworkInterfaceIndex(interfaceName, interfaces) {
for (var i = 0; i < interfaces.length; i++) {
if (interfaces[i].name == interfaceName) {
return i;
}
}
return -1;
}
// Draw the graph for network tx/rx bytes. // Draw the graph for network tx/rx bytes.
function drawNetworkBytes(elementId, machineInfo, stats) { function drawNetworkBytes(elementId, machineInfo, stats) {
if (stats.spec.has_network && !hasResource(stats, "network")) { if (stats.spec.has_network && !hasResource(stats, "network")) {
return; return;
} }
// Get interface index.
var interfaceIndex = -1;
if (stats.stats.length > 0) {
interfaceIndex = getNetworkInterfaceIndex(window.cadvisor.network.interface, stats.stats[0].network.interfaces);
}
if (interfaceIndex < 0) {
console.log("Unable to find interface\"", interfaceName, "\" in ", stats.stats.network);
return;
}
var titles = ["Time", "Tx bytes", "Rx bytes"]; var titles = ["Time", "Tx bytes", "Rx bytes"];
var data = []; var data = [];
for (var i = 1; i < stats.stats.length; i++) { for (var i = 1; i < stats.stats.length; i++) {
...@@ -390,8 +410,8 @@ function drawNetworkBytes(elementId, machineInfo, stats) { ...@@ -390,8 +410,8 @@ function drawNetworkBytes(elementId, machineInfo, stats) {
var elements = []; var elements = [];
elements.push(cur.timestamp); elements.push(cur.timestamp);
elements.push((cur.network.tx_bytes - prev.network.tx_bytes) / intervalInSec); elements.push((cur.network.interfaces[interfaceIndex].tx_bytes - prev.network.interfaces[interfaceIndex].tx_bytes) / intervalInSec);
elements.push((cur.network.rx_bytes - prev.network.rx_bytes) / intervalInSec); elements.push((cur.network.interfaces[interfaceIndex].rx_bytes - prev.network.interfaces[interfaceIndex].rx_bytes) / intervalInSec);
data.push(elements); data.push(elements);
} }
drawLineChart(titles, data, elementId, "Bytes per second"); drawLineChart(titles, data, elementId, "Bytes per second");
...@@ -403,6 +423,16 @@ function drawNetworkErrors(elementId, machineInfo, stats) { ...@@ -403,6 +423,16 @@ function drawNetworkErrors(elementId, machineInfo, stats) {
return; return;
} }
// Get interface index.
var interfaceIndex = -1;
if (stats.stats.length > 0) {
interfaceIndex = getNetworkInterfaceIndex(window.cadvisor.network.interface, stats.stats[0].network.interfaces);
}
if (interfaceIndex < 0) {
console.log("Unable to find interface\"", interfaceName, "\" in ", stats.stats.network);
return;
}
var titles = ["Time", "Tx", "Rx"]; var titles = ["Time", "Tx", "Rx"];
var data = []; var data = [];
for (var i = 1; i < stats.stats.length; i++) { for (var i = 1; i < stats.stats.length; i++) {
...@@ -412,8 +442,8 @@ function drawNetworkErrors(elementId, machineInfo, stats) { ...@@ -412,8 +442,8 @@ function drawNetworkErrors(elementId, machineInfo, stats) {
var elements = []; var elements = [];
elements.push(cur.timestamp); elements.push(cur.timestamp);
elements.push((cur.network.tx_errors - prev.network.tx_errors) / intervalInSec); elements.push((cur.network.interfaces[interfaceIndex].tx_errors - prev.network.interfaces[interfaceIndex].tx_errors) / intervalInSec);
elements.push((cur.network.rx_errors - prev.network.rx_errors) / intervalInSec); elements.push((cur.network.interfaces[interfaceIndex].rx_errors - prev.network.interfaces[interfaceIndex].rx_errors) / intervalInSec);
data.push(elements); data.push(elements);
} }
drawLineChart(titles, data, elementId, "Errors per second"); drawLineChart(titles, data, elementId, "Errors per second");
...@@ -478,7 +508,7 @@ function drawProcesses(isRoot, rootDir, processInfo) { ...@@ -478,7 +508,7 @@ function drawProcesses(isRoot, rootDir, processInfo) {
var titleTypes = ['string', 'number', 'number', 'string', 'number', 'number', 'number', 'number', 'string', 'string', 'string']; var titleTypes = ['string', 'number', 'number', 'string', 'number', 'number', 'number', 'number', 'string', 'string', 'string'];
var sortIndex = 4 var sortIndex = 4
if (isRoot) { if (isRoot) {
titles.push("Cgroup"); titles.push("Container");
titleTypes.push('string'); titleTypes.push('string');
} }
var data = [] var data = []
...@@ -618,6 +648,60 @@ function drawCharts(machineInfo, containerInfo) { ...@@ -618,6 +648,60 @@ function drawCharts(machineInfo, containerInfo) {
stepExecute(steps); stepExecute(steps);
} }
function setNetwork(interfaceName) {
$("#network-selection-text")
.empty()
.append($("<span>").text("Interface: "))
.append($("<b>").text(interfaceName));
window.cadvisor.network.interface = interfaceName;
// Draw the new stats.
refreshStats();
}
// Creates the network selection dropdown.
function startNetwork(selectionElement, containerInfo) {
if (!hasResource(containerInfo, "network") || containerInfo.stats.length == 0
|| !containerInfo.stats[0].network.interfaces || containerInfo.stats[0].network.interfaces.length == 0) {
return;
}
window.cadvisor.network = {};
window.cadvisor.network.interface = "";
// Add all interfaces to the dropdown.
var el = $("#" + selectionElement);
for (var i = 0; i < containerInfo.stats[0].network.interfaces.length; i++) {
var interfaceName = containerInfo.stats[0].network.interfaces[i].name;
el.append($("<li>")
.attr("role", "presentation")
.append($("<a>")
.attr("role", "menuitem")
.attr("tabindex", -1)
.click(setNetwork.bind(null, interfaceName))
.text(interfaceName)));
}
setNetwork(containerInfo.stats[0].network.interfaces[0].name);
}
// Refresh the stats on the page.
function refreshStats() {
var machineInfo = window.cadvisor.machineInfo;
getStats(window.cadvisor.rootDir, window.cadvisor.containerName, function(containerInfo){
if (window.cadvisor.firstRun) {
window.cadvisor.firstRun = false;
if (containerInfo.spec.has_filesystem) {
startFileSystemUsage("filesystem-usage", machineInfo, containerInfo);
}
if (containerInfo.spec.has_network) {
startNetwork("network-selection", containerInfo);
}
}
drawCharts(machineInfo, containerInfo);
});
}
// Executed when the page finishes loading. // Executed when the page finishes loading.
function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) { function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) {
// Don't fetch data if we don't have any resource. // Don't fetch data if we don't have any resource.
...@@ -628,6 +712,8 @@ function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) { ...@@ -628,6 +712,8 @@ function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) {
window.charts = {}; window.charts = {};
window.cadvisor = {}; window.cadvisor = {};
window.cadvisor.firstRun = true; window.cadvisor.firstRun = true;
window.cadvisor.rootDir = rootDir;
window.cadvisor.containerName = containerName;
// Draw process information at start and refresh every 60s. // Draw process information at start and refresh every 60s.
getProcessInfo(rootDir, containerName, function(processInfo) { getProcessInfo(rootDir, containerName, function(processInfo) {
...@@ -641,15 +727,9 @@ function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) { ...@@ -641,15 +727,9 @@ function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) {
// Get machine info, then get the stats every 1s. // Get machine info, then get the stats every 1s.
getMachineInfo(rootDir, function(machineInfo) { getMachineInfo(rootDir, function(machineInfo) {
window.cadvisor.machineInfo = machineInfo;
setInterval(function() { setInterval(function() {
getStats(rootDir, containerName, function(containerInfo){ refreshStats();
if (window.cadvisor.firstRun && containerInfo.spec.has_filesystem) {
window.cadvisor.firstRun = false;
startFileSystemUsage("filesystem-usage", machineInfo, containerInfo);
}
drawCharts(machineInfo, containerInfo);
});
}, 1000); }, 1000);
}); });
} }
......
...@@ -18,8 +18,10 @@ package static ...@@ -18,8 +18,10 @@ package static
import ( import (
"fmt" "fmt"
"mime"
"net/http" "net/http"
"net/url" "net/url"
"path"
) )
const StaticResource = "/static/" const StaticResource = "/static/"
...@@ -46,6 +48,12 @@ func HandleRequest(w http.ResponseWriter, u *url.URL) error { ...@@ -46,6 +48,12 @@ func HandleRequest(w http.ResponseWriter, u *url.URL) error {
return fmt.Errorf("unknown static resource %q", resource) return fmt.Errorf("unknown static resource %q", resource)
} }
// Set Content-Type if we were able to detect it.
contentType := mime.TypeByExtension(path.Ext(resource))
if contentType != "" {
w.Header().Set("Content-Type", contentType)
}
_, err := w.Write([]byte(content)) _, err := w.Write([]byte(content))
return err return err
} }
...@@ -15,10 +15,6 @@ ...@@ -15,10 +15,6 @@
package bigquery package bigquery
import ( import (
"fmt"
"strconv"
"time"
bigquery "code.google.com/p/google-api-go-client/bigquery/v2" bigquery "code.google.com/p/google-api-go-client/bigquery/v2"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/storage" "github.com/google/cadvisor/storage"
...@@ -248,135 +244,6 @@ func (self *bigqueryStorage) containerFilesystemStatsToRows( ...@@ -248,135 +244,6 @@ func (self *bigqueryStorage) containerFilesystemStatsToRows(
return rows return rows
} }
func convertToUint64(v interface{}) (uint64, error) {
if v == nil {
return 0, nil
}
switch x := v.(type) {
case uint64:
return x, nil
case int:
if x < 0 {
return 0, fmt.Errorf("negative value: %v", x)
}
return uint64(x), nil
case int32:
if x < 0 {
return 0, fmt.Errorf("negative value: %v", x)
}
return uint64(x), nil
case int64:
if x < 0 {
return 0, fmt.Errorf("negative value: %v", x)
}
return uint64(x), nil
case float64:
if x < 0 {
return 0, fmt.Errorf("negative value: %v", x)
}
return uint64(x), nil
case uint32:
return uint64(x), nil
case string:
return strconv.ParseUint(x, 10, 64)
}
return 0, fmt.Errorf("unknown type")
}
func (self *bigqueryStorage) valuesToContainerStats(columns []string, values []interface{}) (*info.ContainerStats, error) {
stats := &info.ContainerStats{
Filesystem: make([]info.FsStats, 0),
}
var err error
for i, col := range columns {
v := values[i]
switch {
case col == colTimestamp:
if t, ok := v.(time.Time); ok {
stats.Timestamp = t
}
case col == colMachineName:
if m, ok := v.(string); ok {
if m != self.machineName {
return nil, fmt.Errorf("different machine")
}
} else {
return nil, fmt.Errorf("machine name field is not a string: %v", v)
}
// Cumulative Cpu Usage
case col == colCpuCumulativeUsage:
stats.Cpu.Usage.Total, err = convertToUint64(v)
// Cumulative Cpu used by the system
case col == colCpuCumulativeUsageSystem:
stats.Cpu.Usage.System, err = convertToUint64(v)
// Cumulative Cpu Usage in user mode
case col == colCpuCumulativeUsageUser:
stats.Cpu.Usage.User, err = convertToUint64(v)
// Memory Usage
case col == colMemoryUsage:
stats.Memory.Usage, err = convertToUint64(v)
// Working set size
case col == colMemoryWorkingSet:
stats.Memory.WorkingSet, err = convertToUint64(v)
// container page fault
case col == colMemoryContainerPgfault:
stats.Memory.ContainerData.Pgfault, err = convertToUint64(v)
// container major page fault
case col == colMemoryContainerPgmajfault:
stats.Memory.ContainerData.Pgmajfault, err = convertToUint64(v)
// hierarchical page fault
case col == colMemoryHierarchicalPgfault:
stats.Memory.HierarchicalData.Pgfault, err = convertToUint64(v)
// hierarchical major page fault
case col == colMemoryHierarchicalPgmajfault:
stats.Memory.HierarchicalData.Pgmajfault, err = convertToUint64(v)
case col == colRxBytes:
stats.Network.RxBytes, err = convertToUint64(v)
case col == colRxErrors:
stats.Network.RxErrors, err = convertToUint64(v)
case col == colTxBytes:
stats.Network.TxBytes, err = convertToUint64(v)
case col == colTxErrors:
stats.Network.TxErrors, err = convertToUint64(v)
case col == colFsDevice:
device, ok := v.(string)
if !ok {
return nil, fmt.Errorf("filesystem name field is not a string: %+v", v)
}
if len(stats.Filesystem) == 0 {
stats.Filesystem = append(stats.Filesystem, info.FsStats{Device: device})
} else {
stats.Filesystem[0].Device = device
}
case col == colFsLimit:
limit, err := convertToUint64(v)
if err != nil {
return nil, fmt.Errorf("filesystem limit field %+v invalid: %s", v, err)
}
if len(stats.Filesystem) == 0 {
stats.Filesystem = append(stats.Filesystem, info.FsStats{Limit: limit})
} else {
stats.Filesystem[0].Limit = limit
}
case col == colFsUsage:
usage, err := convertToUint64(v)
if err != nil {
return nil, fmt.Errorf("filesystem usage field %+v invalid: %s", v, err)
}
if len(stats.Filesystem) == 0 {
stats.Filesystem = append(stats.Filesystem, info.FsStats{Usage: usage})
} else {
stats.Filesystem[0].Usage = usage
}
}
if err != nil {
return nil, fmt.Errorf("column %v has invalid value %v: %v", col, v, err)
}
}
return stats, nil
}
func (self *bigqueryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { func (self *bigqueryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
if stats == nil { if stats == nil {
return nil return nil
...@@ -393,42 +260,6 @@ func (self *bigqueryStorage) AddStats(ref info.ContainerReference, stats *info.C ...@@ -393,42 +260,6 @@ func (self *bigqueryStorage) AddStats(ref info.ContainerReference, stats *info.C
return nil return nil
} }
func (self *bigqueryStorage) getRecentRows(containerName string, numRows int) ([]string, [][]interface{}, error) {
tableName, err := self.client.GetTableName()
if err != nil {
return nil, nil, err
}
query := fmt.Sprintf("SELECT * FROM %v WHERE %v='%v' and %v='%v'", tableName, colContainerName, containerName, colMachineName, self.machineName)
if numRows > 0 {
query = fmt.Sprintf("%v LIMIT %v", query, numRows)
}
return self.client.Query(query)
}
func (self *bigqueryStorage) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
if numStats == 0 {
return nil, nil
}
header, rows, err := self.getRecentRows(containerName, numStats)
if err != nil {
return nil, err
}
statsList := make([]*info.ContainerStats, 0, len(rows))
for _, row := range rows {
stats, err := self.valuesToContainerStats(header, row)
if err != nil {
return nil, err
}
if stats == nil {
continue
}
statsList = append(statsList, stats)
}
return statsList, nil
}
func (self *bigqueryStorage) Close() error { func (self *bigqueryStorage) Close() error {
self.client.Close() self.client.Close()
self.client = nil self.client = nil
......
...@@ -232,56 +232,3 @@ func (c *Client) InsertRow(rowData map[string]interface{}) error { ...@@ -232,56 +232,3 @@ func (c *Client) InsertRow(rowData map[string]interface{}) error {
} }
return nil return nil
} }
// Returns a bigtable table name (format: datasetID.tableID)
func (c *Client) GetTableName() (string, error) {
if c.service == nil || c.datasetId == "" || c.tableId == "" {
return "", fmt.Errorf("table not setup")
}
return fmt.Sprintf("%s.%s", c.datasetId, c.tableId), nil
}
// Do a synchronous query on bigtable and return a header and data rows.
// Number of rows are capped to queryLimit.
func (c *Client) Query(query string) ([]string, [][]interface{}, error) {
service, err := c.getService()
if err != nil {
return nil, nil, err
}
datasetRef := &bigquery.DatasetReference{
DatasetId: c.datasetId,
ProjectId: *projectId,
}
queryRequest := &bigquery.QueryRequest{
DefaultDataset: datasetRef,
MaxResults: queryLimit,
Kind: "json",
Query: query,
}
results, err := service.Jobs.Query(*projectId, queryRequest).Do()
if err != nil {
return nil, nil, err
}
numRows := results.TotalRows
if numRows < 1 {
return nil, nil, fmt.Errorf("query returned no data")
}
headers := []string{}
for _, col := range results.Schema.Fields {
headers = append(headers, col.Name)
}
rows := [][]interface{}{}
numColumns := len(results.Schema.Fields)
for _, data := range results.Rows {
row := make([]interface{}, numColumns)
for c := 0; c < numColumns; c++ {
row[c] = data.F[c].V
}
rows = append(rows, row)
}
return headers, rows, nil
}
...@@ -84,25 +84,4 @@ func main() { ...@@ -84,25 +84,4 @@ func main() {
panic(err) panic(err)
} }
} }
// Query
tableName, err := c.GetTableName()
if err != nil {
fmt.Printf("table not set")
panic(err)
}
query := "SELECT * FROM " + tableName + " ORDER BY Timestamp LIMIT 100"
header, rows, err := c.Query(query)
if err != nil {
fmt.Printf("Failed query")
panic(err)
}
fmt.Printf("Headers: %v", header)
for _, row := range rows {
for i, val := range row {
fmt.Printf("%s:%v ", header[i], val)
}
fmt.Printf("\n")
}
} }
...@@ -139,114 +139,6 @@ func (self *influxdbStorage) containerStatsToValues( ...@@ -139,114 +139,6 @@ func (self *influxdbStorage) containerStatsToValues(
return columns, values return columns, values
} }
func convertToUint64(v interface{}) (uint64, error) {
if v == nil {
return 0, nil
}
switch x := v.(type) {
case uint64:
return x, nil
case int:
if x < 0 {
return 0, fmt.Errorf("negative value: %v", x)
}
return uint64(x), nil
case int32:
if x < 0 {
return 0, fmt.Errorf("negative value: %v", x)
}
return uint64(x), nil
case int64:
if x < 0 {
return 0, fmt.Errorf("negative value: %v", x)
}
return uint64(x), nil
case float64:
if x < 0 {
return 0, fmt.Errorf("negative value: %v", x)
}
return uint64(x), nil
case uint32:
return uint64(x), nil
}
return 0, fmt.Errorf("unknown type")
}
func (self *influxdbStorage) valuesToContainerStats(columns []string, values []interface{}) (*info.ContainerStats, error) {
stats := &info.ContainerStats{
Filesystem: make([]info.FsStats, 0),
}
var err error
for i, col := range columns {
v := values[i]
switch {
case col == colTimestamp:
if f64sec, ok := v.(float64); ok && stats.Timestamp.IsZero() {
stats.Timestamp = time.Unix(int64(f64sec)/1E3, (int64(f64sec)%1E3)*1E6)
}
case col == colMachineName:
if m, ok := v.(string); ok {
if m != self.machineName {
return nil, fmt.Errorf("different machine")
}
} else {
return nil, fmt.Errorf("machine name field is not a string: %v", v)
}
// Cumulative Cpu Usage
case col == colCpuCumulativeUsage:
stats.Cpu.Usage.Total, err = convertToUint64(v)
// Memory Usage
case col == colMemoryUsage:
stats.Memory.Usage, err = convertToUint64(v)
// Working set size
case col == colMemoryWorkingSet:
stats.Memory.WorkingSet, err = convertToUint64(v)
case col == colRxBytes:
stats.Network.RxBytes, err = convertToUint64(v)
case col == colRxErrors:
stats.Network.RxErrors, err = convertToUint64(v)
case col == colTxBytes:
stats.Network.TxBytes, err = convertToUint64(v)
case col == colTxErrors:
stats.Network.TxErrors, err = convertToUint64(v)
case col == colFsDevice:
device, ok := v.(string)
if !ok {
return nil, fmt.Errorf("filesystem name field is not a string: %+v", v)
}
if len(stats.Filesystem) == 0 {
stats.Filesystem = append(stats.Filesystem, info.FsStats{Device: device})
} else {
stats.Filesystem[0].Device = device
}
case col == colFsLimit:
limit, err := convertToUint64(v)
if err != nil {
return nil, fmt.Errorf("filesystem limit field %+v invalid: %s", v, err)
}
if len(stats.Filesystem) == 0 {
stats.Filesystem = append(stats.Filesystem, info.FsStats{Limit: limit})
} else {
stats.Filesystem[0].Limit = limit
}
case col == colFsUsage:
usage, err := convertToUint64(v)
if err != nil {
return nil, fmt.Errorf("filesystem usage field %+v invalid: %s", v, err)
}
if len(stats.Filesystem) == 0 {
stats.Filesystem = append(stats.Filesystem, info.FsStats{Usage: usage})
} else {
stats.Filesystem[0].Usage = usage
}
}
if err != nil {
return nil, fmt.Errorf("column %v has invalid value %v: %v", col, v, err)
}
}
return stats, nil
}
func (self *influxdbStorage) OverrideReadyToFlush(readyToFlush func() bool) { func (self *influxdbStorage) OverrideReadyToFlush(readyToFlush func() bool) {
self.readyToFlush = readyToFlush self.readyToFlush = readyToFlush
} }
...@@ -283,42 +175,6 @@ func (self *influxdbStorage) AddStats(ref info.ContainerReference, stats *info.C ...@@ -283,42 +175,6 @@ func (self *influxdbStorage) AddStats(ref info.ContainerReference, stats *info.C
return nil return nil
} }
func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
if numStats == 0 {
return nil, nil
}
// TODO(dengnan): select only columns that we need
// TODO(dengnan): escape names
query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName)
if numStats > 0 {
query = fmt.Sprintf("%v limit %v", query, numStats)
}
series, err := self.client.Query(query)
if err != nil {
return nil, err
}
statsList := make([]*info.ContainerStats, 0, len(series))
// By default, influxDB returns data in time descending order.
// RecentStats() requires stats in time increasing order,
// so we need to go through from the last one to the first one.
for i := len(series) - 1; i >= 0; i-- {
s := series[i]
for j := len(s.Points) - 1; j >= 0; j-- {
values := s.Points[j]
stats, err := self.valuesToContainerStats(s.Columns, values)
if err != nil {
return nil, err
}
if stats == nil {
continue
}
statsList = append(statsList, stats)
}
}
return statsList, nil
}
func (self *influxdbStorage) Close() error { func (self *influxdbStorage) Close() error {
self.client = nil self.client = nil
return nil return nil
......
...@@ -51,7 +51,7 @@ func (self *influxDbTestStorageDriver) AddStats(ref info.ContainerReference, sta ...@@ -51,7 +51,7 @@ func (self *influxDbTestStorageDriver) AddStats(ref info.ContainerReference, sta
} }
func (self *influxDbTestStorageDriver) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) { func (self *influxDbTestStorageDriver) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
return self.base.RecentStats(containerName, numStats) return nil, nil
} }
func (self *influxDbTestStorageDriver) Percentiles(containerName string, cpuUsagePercentiles []int, memUsagePercentiles []int) (*info.ContainerStatsPercentiles, error) { func (self *influxDbTestStorageDriver) Percentiles(containerName string, cpuUsagePercentiles []int, memUsagePercentiles []int) (*info.ContainerStatsPercentiles, error) {
......
...@@ -89,12 +89,6 @@ func (self *redisStorage) AddStats(ref info.ContainerReference, stats *info.Cont ...@@ -89,12 +89,6 @@ func (self *redisStorage) AddStats(ref info.ContainerReference, stats *info.Cont
return nil return nil
} }
// We just need to push the data to the redis, do not need to pull from the redis,
//so we do not override RecentStats()
func (self *redisStorage) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
return nil, nil
}
func (self *redisStorage) Close() error { func (self *redisStorage) Close() error {
return self.conn.Close() return self.conn.Close()
} }
......
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 statsd
import (
"fmt"
"net"
"github.com/golang/glog"
)
type Client struct {
HostPort string
conn net.Conn
}
func (self *Client) Open() error {
conn, err := net.Dial("udp", self.HostPort)
if err != nil {
glog.Errorf("failed to open udp connection to %q: %v", self.HostPort, err)
return err
}
self.conn = conn
return nil
}
func (self *Client) Close() {
self.conn.Close()
}
func (self *Client) UpdateGauge(name, value string) error {
stats := make(map[string]string)
val := fmt.Sprintf("%s|g", value)
stats[name] = val
if err := self.send(stats); err != nil {
return err
}
return nil
}
// Simple send to statsd daemon without sampling.
func (self *Client) send(data map[string]string) error {
for k, v := range data {
formatted := fmt.Sprintf("%s:%s", k, v)
_, err := fmt.Fprintf(self.conn, formatted)
if err != nil {
glog.V(3).Infof("failed to send data %q: %v", formatted, err)
// return on first error.
return err
}
}
return nil
}
func New(hostPort string) (*Client, error) {
client := Client{HostPort: hostPort}
if err := client.Open(); err != nil {
return nil, err
}
return &client, nil
}
...@@ -19,13 +19,6 @@ import info "github.com/google/cadvisor/info/v1" ...@@ -19,13 +19,6 @@ import info "github.com/google/cadvisor/info/v1"
type StorageDriver interface { type StorageDriver interface {
AddStats(ref info.ContainerReference, stats *info.ContainerStats) error AddStats(ref info.ContainerReference, stats *info.ContainerStats) error
// Read most recent stats. numStats indicates max number of stats
// returned. The returned stats must be consecutive observed stats. If
// numStats < 0, then return all stats stored in the storage. The
// returned stats should be sorted in time increasing order, i.e. Most
// recent stats should be the last.
RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error)
// Close will clear the state of the storage driver. The elements // Close will clear the state of the storage driver. The elements
// stored in the underlying storage may or may not be deleted depending // stored in the underlying storage may or may not be deleted depending
// on the implementation of the storage driver. // on the implementation of the storage driver.
......
...@@ -29,11 +29,6 @@ func (self *MockStorageDriver) AddStats(ref info.ContainerReference, stats *info ...@@ -29,11 +29,6 @@ func (self *MockStorageDriver) AddStats(ref info.ContainerReference, stats *info
return args.Error(0) return args.Error(0)
} }
func (self *MockStorageDriver) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
args := self.Called(containerName, numStats)
return args.Get(0).([]*info.ContainerStats), args.Error(1)
}
func (self *MockStorageDriver) Close() error { func (self *MockStorageDriver) Close() error {
if self.MockCloseMethod { if self.MockCloseMethod {
args := self.Called() args := self.Called()
......
...@@ -147,126 +147,3 @@ func StorageDriverFillRandomStatsFunc( ...@@ -147,126 +147,3 @@ func StorageDriverFillRandomStatsFunc(
} }
} }
} }
func StorageDriverTestRetrievePartialRecentStats(driver TestStorageDriver, t *testing.T) {
defer driver.Close()
N := 100
memTrace := make([]uint64, N)
cpuTrace := make([]uint64, N)
for i := 0; i < N; i++ {
memTrace[i] = uint64(i + 1)
cpuTrace[i] = uint64(1)
}
ref := info.ContainerReference{
Name: "container",
}
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
for _, stats := range trace {
driver.AddStats(ref, stats)
}
recentStats, err := driver.RecentStats(ref.Name, 10)
if err != nil {
t.Fatal(err)
}
if len(recentStats) == 0 {
t.Fatal("should at least store one stats")
}
if len(recentStats) > 10 {
t.Fatalf("returned %v stats, not 10.", len(recentStats))
}
actualRecentStats := trace[len(trace)-len(recentStats):]
// The returned stats should be sorted in time increasing order
for i, s := range actualRecentStats {
r := recentStats[i]
if !driver.StatsEq(s, r) {
t.Errorf("unexpected stats %+v with memory usage %v; should be %+v", r, r.Memory.Usage, s)
}
}
}
func StorageDriverTestRetrieveAllRecentStats(driver TestStorageDriver, t *testing.T) {
defer driver.Close()
N := 100
memTrace := make([]uint64, N)
cpuTrace := make([]uint64, N)
for i := 0; i < N; i++ {
memTrace[i] = uint64(i + 1)
cpuTrace[i] = uint64(1)
}
ref := info.ContainerReference{
Name: "container",
}
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
for _, stats := range trace {
driver.AddStats(ref, stats)
}
recentStats, err := driver.RecentStats(ref.Name, -1)
if err != nil {
t.Fatal(err)
}
if len(recentStats) == 0 {
t.Fatal("should at least store one stats")
}
if len(recentStats) > N {
t.Fatalf("returned %v stats, not %d.", len(recentStats), N)
}
actualRecentStats := trace[len(trace)-len(recentStats):]
// The returned stats should be sorted in time increasing order
for i, s := range actualRecentStats {
r := recentStats[i]
if !driver.StatsEq(s, r) {
t.Errorf("unexpected stats %+v with memory usage %v", r, r.Memory.Usage)
}
}
}
func StorageDriverTestNoRecentStats(driver TestStorageDriver, t *testing.T) {
defer driver.Close()
nonExistContainer := "somerandomecontainer"
stats, _ := driver.RecentStats(nonExistContainer, -1)
if len(stats) > 0 {
t.Errorf("RecentStats() returns %v stats on non exist container", len(stats))
}
}
func StorageDriverTestRetrieveZeroRecentStats(driver TestStorageDriver, t *testing.T) {
defer driver.Close()
N := 100
memTrace := make([]uint64, N)
cpuTrace := make([]uint64, N)
for i := 0; i < N; i++ {
memTrace[i] = uint64(i + 1)
cpuTrace[i] = uint64(1)
}
ref := info.ContainerReference{
Name: "container",
}
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
for _, stats := range trace {
driver.AddStats(ref, stats)
}
recentStats, err := driver.RecentStats(ref.Name, 0)
if err != nil {
t.Fatal(err)
}
if len(recentStats) > 0 {
t.Errorf("RecentStats() returns %v stats when requests for 0 stats", len(recentStats))
}
}
...@@ -19,7 +19,7 @@ import ( ...@@ -19,7 +19,7 @@ import (
) )
// Manages a buffer of usage samples. // Manages a buffer of usage samples.
// This is similar to stats buffer in storage/memory. // This is similar to stats buffer in cache/memory.
// The main difference is that we do not pre-allocate the buffer as most containers // The main difference is that we do not pre-allocate the buffer as most containers
// won't live that long. // won't live that long.
type SamplesBuffer struct { type SamplesBuffer struct {
......
...@@ -24,10 +24,11 @@ import ( ...@@ -24,10 +24,11 @@ import (
) )
const ( const (
blockDir = "/sys/block" blockDir = "/sys/block"
cacheDir = "/sys/devices/system/cpu/cpu" cacheDir = "/sys/devices/system/cpu/cpu"
netDir = "/sys/class/net" netDir = "/sys/class/net"
dmiDir = "/sys/class/dmi" dmiDir = "/sys/class/dmi"
ppcDevTree = "/proc/device-tree"
) )
type CacheInfo struct { type CacheInfo struct {
...@@ -235,7 +236,15 @@ func (self *realSysFs) GetCacheInfo(id int, name string) (CacheInfo, error) { ...@@ -235,7 +236,15 @@ func (self *realSysFs) GetCacheInfo(id int, name string) (CacheInfo, error) {
func (self *realSysFs) GetSystemUUID() (string, error) { func (self *realSysFs) GetSystemUUID() (string, error) {
id, err := ioutil.ReadFile(path.Join(dmiDir, "id", "product_uuid")) id, err := ioutil.ReadFile(path.Join(dmiDir, "id", "product_uuid"))
if err != nil { if err != nil {
return "", err //If running on baremetal Power then UID is /proc/device-tree/system-id
id, err = ioutil.ReadFile(path.Join(ppcDevTree, "system-id"))
if err != nil {
//If running on a KVM guest on Power then UUID is /proc/device-tree/vm,uuid
id, err = ioutil.ReadFile(path.Join(ppcDevTree, "vm,uuid"))
if err != nil {
return "", err
}
}
} }
return strings.TrimSpace(string(id)), nil return strings.TrimSpace(string(id)), nil
} }
...@@ -157,19 +157,19 @@ func GetCacheInfo(sysFs sysfs.SysFs, id int) ([]sysfs.CacheInfo, error) { ...@@ -157,19 +157,19 @@ func GetCacheInfo(sysFs sysfs.SysFs, id int) ([]sysfs.CacheInfo, error) {
return info, nil return info, nil
} }
func GetNetworkStats(name string) (info.NetworkStats, error) { func GetNetworkStats(name string) (info.InterfaceStats, error) {
stats := info.NetworkStats{}
// TODO(rjnagal): Take syfs as an argument. // TODO(rjnagal): Take syfs as an argument.
sysFs, err := sysfs.NewRealSysFs() sysFs, err := sysfs.NewRealSysFs()
if err != nil { if err != nil {
return stats, err return info.InterfaceStats{}, err
} }
return getNetworkStats(name, sysFs) return getNetworkStats(name, sysFs)
} }
func getNetworkStats(name string, sysFs sysfs.SysFs) (info.NetworkStats, error) { func getNetworkStats(name string, sysFs sysfs.SysFs) (info.InterfaceStats, error) {
stats := info.NetworkStats{} var stats info.InterfaceStats
var err error var err error
stats.Name = name
stats.RxBytes, err = sysFs.GetNetworkStatValue(name, "rx_bytes") stats.RxBytes, err = sysFs.GetNetworkStatValue(name, "rx_bytes")
if err != nil { if err != nil {
return stats, err return stats, err
......
...@@ -110,7 +110,8 @@ func TestGetCacheInfo(t *testing.T) { ...@@ -110,7 +110,8 @@ func TestGetCacheInfo(t *testing.T) {
} }
func TestGetNetworkStats(t *testing.T) { func TestGetNetworkStats(t *testing.T) {
expected_stats := info.NetworkStats{ expected_stats := info.InterfaceStats{
Name: "eth0",
RxBytes: 1024, RxBytes: 1024,
RxPackets: 1024, RxPackets: 1024,
RxErrors: 1024, RxErrors: 1024,
......
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
package version package version
// Version of cAdvisor. // Version of cAdvisor.
const VERSION = "0.14.0" const VERSION = "0.15.1"
language: go
go:
- 1.4
install:
- export GOPATH="$HOME/gopath"
- mkdir -p "$GOPATH/src/google.golang.org"
- mv "$TRAVIS_BUILD_DIR" "$GOPATH/src/google.golang.org/appengine"
- go get -v -t -d google.golang.org/appengine/...
script:
- go test -v google.golang.org/appengine/...
- go test -v -race google.golang.org/appengine/...
# Go App Engine for Managed VMs
[![Build Status](https://travis-ci.org/golang/appengine.svg)](https://travis-ci.org/golang/appengine)
This repository supports the Go runtime for Managed VMs on App Engine.
It provides APIs for interacting with App Engine services.
Its canonical import path is `google.golang.org/appengine`.
See https://cloud.google.com/appengine/docs/go/managed-vms/
for more information.
## Directory structure
The top level directory of this repository is the `appengine` package. It
contains the
basic types (e.g. `appengine.Context`) that are used across APIs. Specific API
packages are in subdirectories (e.g. `datastore`).
There is an `internal` subdirectory that contains service protocol buffers,
plus packages required for connectivity to make API calls. App Engine apps
should not directly import any package under `internal`.
## Updating a Go App Engine app
This section describes how to update a traditional Go App Engine app to run on Managed VMs.
### 1. Update YAML files
The `app.yaml` file (and YAML files for modules) should have these new lines added:
```
vm: true
manual_scaling:
instances: 1
```
See https://cloud.google.com/appengine/docs/go/modules/#Go_Instance_scaling_and_class for details.
### 2. Update import paths
The import paths for App Engine packages are now fully qualified, based at `google.golang.org/appengine`.
You will need to update your code to use import paths starting with that; for instance,
code importing `appengine/datastore` will now need to import `google.golang.org/appengine/datastore`.
You can do that manually, or by running this command to recursively update all Go source files in the current directory:
(may require GNU sed)
```
sed -i '/"appengine/{s,"appengine,"google.golang.org/appengine,;s,appengine_,appengine/,}' \
$(find . -name '*.go')
```
### 3. Update code using deprecated, removed or modified APIs
Most App Engine services are available with exactly the same API.
A few APIs were cleaned up, and some are not available yet.
This list summarises the differences:
* `appengine.Datacenter` now takes an `appengine.Context` argument.
* `datastore.PropertyLoadSaver` has been simplified to use slices in place of channels.
* `search.FieldLoadSaver` now handles document metadata.
* `taskqueue.QueueStats` no longer takes a maxTasks argument. That argument has been
deprecated and unused for a long time.
* `appengine/aetest`, `appengine/blobstore`, `appengine/cloudsql`
and `appengine/runtime` have not been ported yet.
* `appengine.BackendHostname` and `appengine.BackendInstance` were for the deprecated backends feature.
Use `appengine.ModuleHostname`and `appengine.ModuleName` instead.
* `appengine.IsCapabilityDisabled` and `appengine/capability` are obsolete.
* Most of `appengine/file` is deprecated. Use [Google Cloud Storage](https://godoc.org/google.golang.org/cloud/storage) instead.
* `appengine/socket` is deprecated. Use the standard `net` package instead.
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// Package appengine provides basic functionality for Google App Engine.
//
// For more information on how to write Go apps for Google App Engine, see:
// https://cloud.google.com/appengine/docs/go/
package appengine
import (
"net/http"
"github.com/golang/protobuf/proto"
"google.golang.org/appengine/internal"
)
// IsDevAppServer reports whether the App Engine app is running in the
// development App Server.
func IsDevAppServer() bool {
// TODO(dsymonds): Detect this.
return false
}
// Context represents the context of an in-flight HTTP request.
type Context interface {
// Debugf formats its arguments according to the format, analogous to fmt.Printf,
// and records the text as a log message at Debug level.
Debugf(format string, args ...interface{})
// Infof is like Debugf, but at Info level.
Infof(format string, args ...interface{})
// Warningf is like Debugf, but at Warning level.
Warningf(format string, args ...interface{})
// Errorf is like Debugf, but at Error level.
Errorf(format string, args ...interface{})
// Criticalf is like Debugf, but at Critical level.
Criticalf(format string, args ...interface{})
// The remaining methods are for internal use only.
// Developer-facing APIs wrap these methods to provide a more friendly API.
// Internal use only.
Call(service, method string, in, out proto.Message, opts *internal.CallOptions) error
// Internal use only. Use AppID instead.
FullyQualifiedAppID() string
// Internal use only.
Request() interface{}
}
// NewContext returns a context for an in-flight HTTP request.
// Repeated calls will return the same value.
func NewContext(req *http.Request) Context {
return internal.NewContext(req)
}
// TODO(dsymonds): Add BackgroundContext function?
// BlobKey is a key for a blobstore blob.
//
// Conceptually, this type belongs in the blobstore package, but it lives in
// the appengine package to avoid a circular dependency: blobstore depends on
// datastore, and datastore needs to refer to the BlobKey type.
type BlobKey string
// GeoPoint represents a location as latitude/longitude in degrees.
type GeoPoint struct {
Lat, Lng float64
}
// Valid returns whether a GeoPoint is within [-90, 90] latitude and [-180, 180] longitude.
func (g GeoPoint) Valid() bool {
return -90 <= g.Lat && g.Lat <= 90 && -180 <= g.Lng && g.Lng <= 180
}
package appengine
import (
"testing"
)
func TestValidGeoPoint(t *testing.T) {
testCases := []struct {
desc string
pt GeoPoint
want bool
}{
{
"valid",
GeoPoint{67.21, 13.37},
true,
},
{
"high lat",
GeoPoint{-90.01, 13.37},
false,
},
{
"low lat",
GeoPoint{90.01, 13.37},
false,
},
{
"high lng",
GeoPoint{67.21, 182},
false,
},
{
"low lng",
GeoPoint{67.21, -181},
false,
},
}
for _, tc := range testCases {
if got := tc.pt.Valid(); got != tc.want {
t.Errorf("%s: got %v, want %v", tc.desc, got, tc.want)
}
}
}
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package channel implements the server side of App Engine's Channel API.
Create creates a new channel associated with the given clientID,
which must be unique to the client that will use the returned token.
token, err := channel.Create(c, "player1")
if err != nil {
// handle error
}
// return token to the client in an HTTP response
Send sends a message to the client over the channel identified by clientID.
channel.Send(c, "player1", "Game over!")
*/
package channel
import (
"encoding/json"
"google.golang.org/appengine"
"google.golang.org/appengine/internal"
basepb "google.golang.org/appengine/internal/base"
pb "google.golang.org/appengine/internal/channel"
)
// Create creates a channel and returns a token for use by the client.
// The clientID is an application-provided string used to identify the client.
func Create(c appengine.Context, clientID string) (token string, err error) {
req := &pb.CreateChannelRequest{
ApplicationKey: &clientID,
}
resp := &pb.CreateChannelResponse{}
err = c.Call(service, "CreateChannel", req, resp, nil)
token = resp.GetToken()
return token, remapError(err)
}
// Send sends a message on the channel associated with clientID.
func Send(c appengine.Context, clientID, message string) error {
req := &pb.SendMessageRequest{
ApplicationKey: &clientID,
Message: &message,
}
resp := &basepb.VoidProto{}
return remapError(c.Call(service, "SendChannelMessage", req, resp, nil))
}
// SendJSON is a helper function that sends a JSON-encoded value
// on the channel associated with clientID.
func SendJSON(c appengine.Context, clientID string, value interface{}) error {
m, err := json.Marshal(value)
if err != nil {
return err
}
return Send(c, clientID, string(m))
}
// remapError fixes any APIError referencing "xmpp" into one referencing "channel".
func remapError(err error) error {
if e, ok := err.(*internal.APIError); ok {
if e.Service == "xmpp" {
e.Service = "channel"
}
}
return err
}
var service = "xmpp" // prod
func init() {
if appengine.IsDevAppServer() {
service = "channel" // dev
}
internal.RegisterErrorCodeMap("channel", pb.ChannelServiceError_ErrorCode_name)
}
package channel
import (
"testing"
"google.golang.org/appengine/internal"
)
func TestRemapError(t *testing.T) {
err := &internal.APIError{
Service: "xmpp",
}
err = remapError(err).(*internal.APIError)
if err.Service != "channel" {
t.Errorf("err.Service = %q, want %q", err.Service, "channel")
}
}
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package datastore
import (
"bytes"
"encoding/base64"
"encoding/gob"
"errors"
"fmt"
"strconv"
"strings"
"github.com/golang/protobuf/proto"
"google.golang.org/appengine"
"google.golang.org/appengine/internal"
pb "google.golang.org/appengine/internal/datastore"
)
// Key represents the datastore key for a stored entity, and is immutable.
type Key struct {
kind string
stringID string
intID int64
parent *Key
appID string
namespace string
}
// Kind returns the key's kind (also known as entity type).
func (k *Key) Kind() string {
return k.kind
}
// StringID returns the key's string ID (also known as an entity name or key
// name), which may be "".
func (k *Key) StringID() string {
return k.stringID
}
// IntID returns the key's integer ID, which may be 0.
func (k *Key) IntID() int64 {
return k.intID
}
// Parent returns the key's parent key, which may be nil.
func (k *Key) Parent() *Key {
return k.parent
}
// AppID returns the key's application ID.
func (k *Key) AppID() string {
return k.appID
}
// Namespace returns the key's namespace.
func (k *Key) Namespace() string {
return k.namespace
}
// Incomplete returns whether the key does not refer to a stored entity.
// In particular, whether the key has a zero StringID and a zero IntID.
func (k *Key) Incomplete() bool {
return k.stringID == "" && k.intID == 0
}
// valid returns whether the key is valid.
func (k *Key) valid() bool {
if k == nil {
return false
}
for ; k != nil; k = k.parent {
if k.kind == "" || k.appID == "" {
return false
}
if k.stringID != "" && k.intID != 0 {
return false
}
if k.parent != nil {
if k.parent.Incomplete() {
return false
}
if k.parent.appID != k.appID || k.parent.namespace != k.namespace {
return false
}
}
}
return true
}
// Equal returns whether two keys are equal.
func (k *Key) Equal(o *Key) bool {
for k != nil && o != nil {
if k.kind != o.kind || k.stringID != o.stringID || k.intID != o.intID || k.appID != o.appID || k.namespace != o.namespace {
return false
}
k, o = k.parent, o.parent
}
return k == o
}
// root returns the furthest ancestor of a key, which may be itself.
func (k *Key) root() *Key {
for k.parent != nil {
k = k.parent
}
return k
}
// marshal marshals the key's string representation to the buffer.
func (k *Key) marshal(b *bytes.Buffer) {
if k.parent != nil {
k.parent.marshal(b)
}
b.WriteByte('/')
b.WriteString(k.kind)
b.WriteByte(',')
if k.stringID != "" {
b.WriteString(k.stringID)
} else {
b.WriteString(strconv.FormatInt(k.intID, 10))
}
}
// String returns a string representation of the key.
func (k *Key) String() string {
if k == nil {
return ""
}
b := bytes.NewBuffer(make([]byte, 0, 512))
k.marshal(b)
return b.String()
}
type gobKey struct {
Kind string
StringID string
IntID int64
Parent *gobKey
AppID string
Namespace string
}
func keyToGobKey(k *Key) *gobKey {
if k == nil {
return nil
}
return &gobKey{
Kind: k.kind,
StringID: k.stringID,
IntID: k.intID,
Parent: keyToGobKey(k.parent),
AppID: k.appID,
Namespace: k.namespace,
}
}
func gobKeyToKey(gk *gobKey) *Key {
if gk == nil {
return nil
}
return &Key{
kind: gk.Kind,
stringID: gk.StringID,
intID: gk.IntID,
parent: gobKeyToKey(gk.Parent),
appID: gk.AppID,
namespace: gk.Namespace,
}
}
func (k *Key) GobEncode() ([]byte, error) {
buf := new(bytes.Buffer)
if err := gob.NewEncoder(buf).Encode(keyToGobKey(k)); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (k *Key) GobDecode(buf []byte) error {
gk := new(gobKey)
if err := gob.NewDecoder(bytes.NewBuffer(buf)).Decode(gk); err != nil {
return err
}
*k = *gobKeyToKey(gk)
return nil
}
func (k *Key) MarshalJSON() ([]byte, error) {
return []byte(`"` + k.Encode() + `"`), nil
}
func (k *Key) UnmarshalJSON(buf []byte) error {
if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
return errors.New("datastore: bad JSON key")
}
k2, err := DecodeKey(string(buf[1 : len(buf)-1]))
if err != nil {
return err
}
*k = *k2
return nil
}
// Encode returns an opaque representation of the key
// suitable for use in HTML and URLs.
// This is compatible with the Python and Java runtimes.
func (k *Key) Encode() string {
ref := keyToProto("", k)
b, err := proto.Marshal(ref)
if err != nil {
panic(err)
}
// Trailing padding is stripped.
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
// DecodeKey decodes a key from the opaque representation returned by Encode.
func DecodeKey(encoded string) (*Key, error) {
// Re-add padding.
if m := len(encoded) % 4; m != 0 {
encoded += strings.Repeat("=", 4-m)
}
b, err := base64.URLEncoding.DecodeString(encoded)
if err != nil {
return nil, err
}
ref := new(pb.Reference)
if err := proto.Unmarshal(b, ref); err != nil {
return nil, err
}
return protoToKey(ref)
}
// NewIncompleteKey creates a new incomplete key.
// kind cannot be empty.
func NewIncompleteKey(c appengine.Context, kind string, parent *Key) *Key {
return NewKey(c, kind, "", 0, parent)
}
// NewKey creates a new key.
// kind cannot be empty.
// Either one or both of stringID and intID must be zero. If both are zero,
// the key returned is incomplete.
// parent must either be a complete key or nil.
func NewKey(c appengine.Context, kind, stringID string, intID int64, parent *Key) *Key {
// If there's a parent key, use its namespace.
// Otherwise, do a fake RPC to try to get a namespace if c is a namespacedContext (or wraps one).
var namespace string
if parent != nil {
namespace = parent.namespace
} else {
namespace = internal.VirtAPI(c, "GetNamespace")
}
return &Key{
kind: kind,
stringID: stringID,
intID: intID,
parent: parent,
appID: c.FullyQualifiedAppID(),
namespace: namespace,
}
}
// AllocateIDs returns a range of n integer IDs with the given kind and parent
// combination. kind cannot be empty; parent may be nil. The IDs in the range
// returned will not be used by the datastore's automatic ID sequence generator
// and may be used with NewKey without conflict.
//
// The range is inclusive at the low end and exclusive at the high end. In
// other words, valid intIDs x satisfy low <= x && x < high.
//
// If no error is returned, low + n == high.
func AllocateIDs(c appengine.Context, kind string, parent *Key, n int) (low, high int64, err error) {
if kind == "" {
return 0, 0, errors.New("datastore: AllocateIDs given an empty kind")
}
if n < 0 {
return 0, 0, fmt.Errorf("datastore: AllocateIDs given a negative count: %d", n)
}
if n == 0 {
return 0, 0, nil
}
req := &pb.AllocateIdsRequest{
ModelKey: keyToProto("", NewIncompleteKey(c, kind, parent)),
Size: proto.Int64(int64(n)),
}
res := &pb.AllocateIdsResponse{}
if err := c.Call("datastore_v3", "AllocateIds", req, res, nil); err != nil {
return 0, 0, err
}
// The protobuf is inclusive at both ends. Idiomatic Go (e.g. slices, for loops)
// is inclusive at the low end and exclusive at the high end, so we add 1.
low = res.GetStart()
high = res.GetEnd() + 1
if low+int64(n) != high {
return 0, 0, fmt.Errorf("datastore: internal error: could not allocate %d IDs", n)
}
return low, high, nil
}
// Copyright 2011 Google Inc. All Rights Reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package datastore
import (
"bytes"
"encoding/gob"
"encoding/json"
"testing"
"github.com/golang/protobuf/proto"
"google.golang.org/appengine"
"google.golang.org/appengine/internal"
)
func TestKeyEncoding(t *testing.T) {
testCases := []struct {
desc string
key *Key
exp string
}{
{
desc: "A simple key with an int ID",
key: &Key{
kind: "Person",
intID: 1,
appID: "glibrary",
},
exp: "aghnbGlicmFyeXIMCxIGUGVyc29uGAEM",
},
{
desc: "A simple key with a string ID",
key: &Key{
kind: "Graph",
stringID: "graph:7-day-active",
appID: "glibrary",
},
exp: "aghnbGlicmFyeXIdCxIFR3JhcGgiEmdyYXBoOjctZGF5LWFjdGl2ZQw",
},
{
desc: "A key with a parent",
key: &Key{
kind: "WordIndex",
intID: 1033,
parent: &Key{
kind: "WordIndex",
intID: 1020032,
appID: "glibrary",
},
appID: "glibrary",
},
exp: "aghnbGlicmFyeXIhCxIJV29yZEluZGV4GIChPgwLEglXb3JkSW5kZXgYiQgM",
},
}
for _, tc := range testCases {
enc := tc.key.Encode()
if enc != tc.exp {
t.Errorf("%s: got %q, want %q", tc.desc, enc, tc.exp)
}
key, err := DecodeKey(tc.exp)
if err != nil {
t.Errorf("%s: failed decoding key: %v", tc.desc, err)
continue
}
if !key.Equal(tc.key) {
t.Errorf("%s: decoded key %v, want %v", tc.desc, key, tc.key)
}
}
}
func TestKeyGob(t *testing.T) {
k := &Key{
kind: "Gopher",
intID: 3,
parent: &Key{
kind: "Mom",
stringID: "narwhal",
appID: "gopher-con",
},
appID: "gopher-con",
}
buf := new(bytes.Buffer)
if err := gob.NewEncoder(buf).Encode(k); err != nil {
t.Fatalf("gob encode failed: %v", err)
}
k2 := new(Key)
if err := gob.NewDecoder(buf).Decode(k2); err != nil {
t.Fatalf("gob decode failed: %v", err)
}
if !k2.Equal(k) {
t.Errorf("gob round trip of %v produced %v", k, k2)
}
}
func TestNilKeyGob(t *testing.T) {
type S struct {
Key *Key
}
s1 := new(S)
buf := new(bytes.Buffer)
if err := gob.NewEncoder(buf).Encode(s1); err != nil {
t.Fatalf("gob encode failed: %v", err)
}
s2 := new(S)
if err := gob.NewDecoder(buf).Decode(s2); err != nil {
t.Fatalf("gob decode failed: %v", err)
}
if s2.Key != nil {
t.Errorf("gob round trip of nil key produced %v", s2.Key)
}
}
func TestKeyJSON(t *testing.T) {
k := &Key{
kind: "Gopher",
intID: 2,
parent: &Key{
kind: "Mom",
stringID: "narwhal",
appID: "gopher-con",
},
appID: "gopher-con",
}
exp := `"` + k.Encode() + `"`
buf, err := json.Marshal(k)
if err != nil {
t.Fatalf("json.Marshal failed: %v", err)
}
if s := string(buf); s != exp {
t.Errorf("JSON encoding of key %v: got %q, want %q", k, s, exp)
}
k2 := new(Key)
if err := json.Unmarshal(buf, k2); err != nil {
t.Fatalf("json.Unmarshal failed: %v", err)
}
if !k2.Equal(k) {
t.Errorf("JSON round trip of %v produced %v", k, k2)
}
}
func TestNilKeyJSON(t *testing.T) {
type S struct {
Key *Key
}
s1 := new(S)
buf, err := json.Marshal(s1)
if err != nil {
t.Fatalf("json.Marshal failed: %v", err)
}
s2 := new(S)
if err := json.Unmarshal(buf, s2); err != nil {
t.Fatalf("json.Unmarshal failed: %v", err)
}
if s2.Key != nil {
t.Errorf("JSON round trip of nil key produced %v", s2.Key)
}
}
type fakeKeyer struct {
appengine.Context
}
func (fakeKeyer) FullyQualifiedAppID() string { return "s~some-app" }
func (fakeKeyer) Call(service, method string, in, out proto.Message, opts *internal.CallOptions) error {
return nil
}
func TestIncompleteKeyWithParent(t *testing.T) {
var c appengine.Context = fakeKeyer{}
// fadduh is a complete key.
fadduh := NewKey(c, "Person", "", 1, nil)
if fadduh.Incomplete() {
t.Fatalf("fadduh is incomplete")
}
// robert is an incomplete key with fadduh as a parent.
robert := NewIncompleteKey(c, "Person", fadduh)
if !robert.Incomplete() {
t.Fatalf("robert is complete")
}
// Both should be valid keys.
if !fadduh.valid() {
t.Errorf("fadduh is invalid: %v", fadduh)
}
if !robert.valid() {
t.Errorf("robert is invalid: %v", robert)
}
}
func TestNamespace(t *testing.T) {
key := &Key{
kind: "Person",
intID: 1,
appID: "s~some-app",
namespace: "mynamespace",
}
if g, w := key.Namespace(), "mynamespace"; g != w {
t.Errorf("key.Namespace() = %q, want %q", g, w)
}
}
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package datastore
import (
"fmt"
"reflect"
"time"
"google.golang.org/appengine"
pb "google.golang.org/appengine/internal/datastore"
)
var (
typeOfBlobKey = reflect.TypeOf(appengine.BlobKey(""))
typeOfByteSlice = reflect.TypeOf([]byte(nil))
typeOfByteString = reflect.TypeOf(ByteString(nil))
typeOfGeoPoint = reflect.TypeOf(appengine.GeoPoint{})
typeOfTime = reflect.TypeOf(time.Time{})
)
// typeMismatchReason returns a string explaining why the property p could not
// be stored in an entity field of type v.Type().
func typeMismatchReason(p Property, v reflect.Value) string {
entityType := "empty"
switch p.Value.(type) {
case int64:
entityType = "int"
case bool:
entityType = "bool"
case string:
entityType = "string"
case float64:
entityType = "float"
case *Key:
entityType = "*datastore.Key"
case time.Time:
entityType = "time.Time"
case appengine.BlobKey:
entityType = "appengine.BlobKey"
case appengine.GeoPoint:
entityType = "appengine.GeoPoint"
case ByteString:
entityType = "datastore.ByteString"
case []byte:
entityType = "[]byte"
}
return fmt.Sprintf("type mismatch: %s versus %v", entityType, v.Type())
}
type propertyLoader struct {
// m holds the number of times a substruct field like "Foo.Bar.Baz" has
// been seen so far. The map is constructed lazily.
m map[string]int
}
func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p Property, requireSlice bool) string {
var v reflect.Value
// Traverse a struct's struct-typed fields.
for name := p.Name; ; {
decoder, ok := codec.byName[name]
if !ok {
return "no such struct field"
}
v = structValue.Field(decoder.index)
if !v.IsValid() {
return "no such struct field"
}
if !v.CanSet() {
return "cannot set struct field"
}
if decoder.substructCodec == nil {
break
}
if v.Kind() == reflect.Slice {
if l.m == nil {
l.m = make(map[string]int)
}
index := l.m[p.Name]
l.m[p.Name] = index + 1
for v.Len() <= index {
v.Set(reflect.Append(v, reflect.New(v.Type().Elem()).Elem()))
}
structValue = v.Index(index)
requireSlice = false
} else {
structValue = v
}
// Strip the "I." from "I.X".
name = name[len(codec.byIndex[decoder.index].name):]
codec = decoder.substructCodec
}
var slice reflect.Value
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
slice = v
v = reflect.New(v.Type().Elem()).Elem()
} else if requireSlice {
return "multiple-valued property requires a slice field type"
}
// Convert indexValues to a Go value with a meaning derived from the
// destination type.
pValue := p.Value
if iv, ok := pValue.(indexValue); ok {
meaning := pb.Property_NO_MEANING
switch v.Type() {
case typeOfBlobKey:
meaning = pb.Property_BLOBKEY
case typeOfByteSlice:
meaning = pb.Property_BLOB
case typeOfByteString:
meaning = pb.Property_BYTESTRING
case typeOfGeoPoint:
meaning = pb.Property_GEORSS_POINT
case typeOfTime:
meaning = pb.Property_GD_WHEN
}
var err error
pValue, err = propValue(iv.value, meaning)
if err != nil {
return err.Error()
}
}
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x, ok := pValue.(int64)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
if v.OverflowInt(x) {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
}
v.SetInt(x)
case reflect.Bool:
x, ok := pValue.(bool)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
v.SetBool(x)
case reflect.String:
switch x := pValue.(type) {
case appengine.BlobKey:
v.SetString(string(x))
case ByteString:
v.SetString(string(x))
case string:
v.SetString(x)
default:
if pValue != nil {
return typeMismatchReason(p, v)
}
}
case reflect.Float32, reflect.Float64:
x, ok := pValue.(float64)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
if v.OverflowFloat(x) {
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
}
v.SetFloat(x)
case reflect.Ptr:
x, ok := pValue.(*Key)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
if _, ok := v.Interface().(*Key); !ok {
return typeMismatchReason(p, v)
}
v.Set(reflect.ValueOf(x))
case reflect.Struct:
switch v.Type() {
case typeOfTime:
x, ok := pValue.(time.Time)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
v.Set(reflect.ValueOf(x))
case typeOfGeoPoint:
x, ok := pValue.(appengine.GeoPoint)
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
v.Set(reflect.ValueOf(x))
default:
return typeMismatchReason(p, v)
}
case reflect.Slice:
x, ok := pValue.([]byte)
if !ok {
if y, yok := pValue.(ByteString); yok {
x, ok = []byte(y), true
}
}
if !ok && pValue != nil {
return typeMismatchReason(p, v)
}
if v.Type().Elem().Kind() != reflect.Uint8 {
return typeMismatchReason(p, v)
}
v.SetBytes(x)
default:
return typeMismatchReason(p, v)
}
if slice.IsValid() {
slice.Set(reflect.Append(slice, v))
}
return ""
}
// loadEntity loads an EntityProto into PropertyLoadSaver or struct pointer.
func loadEntity(dst interface{}, src *pb.EntityProto) (err error) {
props, err := protoToProperties(src)
if err != nil {
return err
}
if e, ok := dst.(PropertyLoadSaver); ok {
return e.Load(props)
}
return LoadStruct(dst, props)
}
func (s structPLS) Load(props []Property) error {
var fieldName, reason string
var l propertyLoader
for _, p := range props {
if errStr := l.load(s.codec, s.v, p, p.Multiple); errStr != "" {
// We don't return early, as we try to load as many properties as possible.
// It is valid to load an entity into a struct that cannot fully represent it.
// That case returns an error, but the caller is free to ignore it.
fieldName, reason = p.Name, errStr
}
}
if reason != "" {
return &ErrFieldMismatch{
StructType: s.v.Type(),
FieldName: fieldName,
Reason: reason,
}
}
return nil
}
func protoToProperties(src *pb.EntityProto) ([]Property, error) {
props, rawProps := src.Property, src.RawProperty
out := make([]Property, 0, len(props)+len(rawProps))
for {
var (
x *pb.Property
noIndex bool
)
if len(props) > 0 {
x, props = props[0], props[1:]
} else if len(rawProps) > 0 {
x, rawProps = rawProps[0], rawProps[1:]
noIndex = true
} else {
break
}
var value interface{}
if x.Meaning != nil && *x.Meaning == pb.Property_INDEX_VALUE {
value = indexValue{x.Value}
} else {
var err error
value, err = propValue(x.Value, x.GetMeaning())
if err != nil {
return nil, err
}
}
out = append(out, Property{
Name: x.GetName(),
Value: value,
NoIndex: noIndex,
Multiple: x.GetMultiple(),
})
}
return out, nil
}
// propValue returns a Go value that combines the raw PropertyValue with a
// meaning. For example, an Int64Value with GD_WHEN becomes a time.Time.
func propValue(v *pb.PropertyValue, m pb.Property_Meaning) (interface{}, error) {
switch {
case v.Int64Value != nil:
if m == pb.Property_GD_WHEN {
return fromUnixMicro(*v.Int64Value), nil
} else {
return *v.Int64Value, nil
}
case v.BooleanValue != nil:
return *v.BooleanValue, nil
case v.StringValue != nil:
if m == pb.Property_BLOB {
return []byte(*v.StringValue), nil
} else if m == pb.Property_BLOBKEY {
return appengine.BlobKey(*v.StringValue), nil
} else if m == pb.Property_BYTESTRING {
return ByteString(*v.StringValue), nil
} else {
return *v.StringValue, nil
}
case v.DoubleValue != nil:
return *v.DoubleValue, nil
case v.Referencevalue != nil:
key, err := referenceValueToKey(v.Referencevalue)
if err != nil {
return nil, err
}
return key, nil
case v.Pointvalue != nil:
// NOTE: Strangely, latitude maps to X, longitude to Y.
return appengine.GeoPoint{Lat: v.Pointvalue.GetX(), Lng: v.Pointvalue.GetY()}, nil
}
return nil, nil
}
// indexValue is a Property value that is created when entities are loaded from
// an index, such as from a projection query.
//
// Such Property values do not contain all of the metadata required to be
// faithfully represented as a Go value, and are instead represented as an
// opaque indexValue. Load the properties into a concrete struct type (e.g. by
// passing a struct pointer to Iterator.Next) to reconstruct actual Go values
// of type int, string, time.Time, etc.
type indexValue struct {
value *pb.PropertyValue
}
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package datastore
import (
"fmt"
"reflect"
"strings"
"sync"
"unicode"
)
// Entities with more than this many indexed properties will not be saved.
const maxIndexedProperties = 5000
// []byte fields more than 1 megabyte long will not be loaded or saved.
const maxBlobLen = 1 << 20
// Property is a name/value pair plus some metadata. A datastore entity's
// contents are loaded and saved as a sequence of Properties. An entity can
// have multiple Properties with the same name, provided that p.Multiple is
// true on all of that entity's Properties with that name.
type Property struct {
// Name is the property name.
Name string
// Value is the property value. The valid types are:
// - int64
// - bool
// - string
// - float64
// - ByteString
// - *Key
// - time.Time
// - appengine.BlobKey
// - appengine.GeoPoint
// - []byte (up to 1 megabyte in length)
// This set is smaller than the set of valid struct field types that the
// datastore can load and save. A Property Value cannot be a slice (apart
// from []byte); use multiple Properties instead. Also, a Value's type
// must be explicitly on the list above; it is not sufficient for the
// underlying type to be on that list. For example, a Value of "type
// myInt64 int64" is invalid. Smaller-width integers and floats are also
// invalid. Again, this is more restrictive than the set of valid struct
// field types.
//
// A Value will have an opaque type when loading entities from an index,
// such as via a projection query. Load entities into a struct instead
// of a PropertyLoadSaver when using a projection query.
//
// A Value may also be the nil interface value; this is equivalent to
// Python's None but not directly representable by a Go struct. Loading
// a nil-valued property into a struct will set that field to the zero
// value.
Value interface{}
// NoIndex is whether the datastore cannot index this property.
NoIndex bool
// Multiple is whether the entity can have multiple properties with
// the same name. Even if a particular instance only has one property with
// a certain name, Multiple should be true if a struct would best represent
// it as a field of type []T instead of type T.
Multiple bool
}
// ByteString is a short byte slice (up to 500 bytes) that can be indexed.
type ByteString []byte
// PropertyLoadSaver can be converted from and to a slice of Properties.
type PropertyLoadSaver interface {
Load([]Property) error
Save() ([]Property, error)
}
// PropertyList converts a []Property to implement PropertyLoadSaver.
type PropertyList []Property
var (
typeOfPropertyLoadSaver = reflect.TypeOf((*PropertyLoadSaver)(nil)).Elem()
typeOfPropertyList = reflect.TypeOf(PropertyList(nil))
)
// Load loads all of the provided properties into l.
// It does not first reset *l to an empty slice.
func (l *PropertyList) Load(p []Property) error {
*l = append(*l, p...)
return nil
}
// Save saves all of l's properties as a slice or Properties.
func (l *PropertyList) Save() ([]Property, error) {
return *l, nil
}
// validPropertyName returns whether name consists of one or more valid Go
// identifiers joined by ".".
func validPropertyName(name string) bool {
if name == "" {
return false
}
for _, s := range strings.Split(name, ".") {
if s == "" {
return false
}
first := true
for _, c := range s {
if first {
first = false
if c != '_' && !unicode.IsLetter(c) {
return false
}
} else {
if c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
}
return true
}
// structTag is the parsed `datastore:"name,options"` tag of a struct field.
// If a field has no tag, or the tag has an empty name, then the structTag's
// name is just the field name. A "-" name means that the datastore ignores
// that field.
type structTag struct {
name string
noIndex bool
}
// structCodec describes how to convert a struct to and from a sequence of
// properties.
type structCodec struct {
// byIndex gives the structTag for the i'th field.
byIndex []structTag
// byName gives the field codec for the structTag with the given name.
byName map[string]fieldCodec
// hasSlice is whether a struct or any of its nested or embedded structs
// has a slice-typed field (other than []byte).
hasSlice bool
// complete is whether the structCodec is complete. An incomplete
// structCodec may be encountered when walking a recursive struct.
complete bool
}
// fieldCodec is a struct field's index and, if that struct field's type is
// itself a struct, that substruct's structCodec.
type fieldCodec struct {
index int
substructCodec *structCodec
}
// structCodecs collects the structCodecs that have already been calculated.
var (
structCodecsMutex sync.Mutex
structCodecs = make(map[reflect.Type]*structCodec)
)
// getStructCodec returns the structCodec for the given struct type.
func getStructCodec(t reflect.Type) (*structCodec, error) {
structCodecsMutex.Lock()
defer structCodecsMutex.Unlock()
return getStructCodecLocked(t)
}
// getStructCodecLocked implements getStructCodec. The structCodecsMutex must
// be held when calling this function.
func getStructCodecLocked(t reflect.Type) (ret *structCodec, retErr error) {
c, ok := structCodecs[t]
if ok {
return c, nil
}
c = &structCodec{
byIndex: make([]structTag, t.NumField()),
byName: make(map[string]fieldCodec),
}
// Add c to the structCodecs map before we are sure it is good. If t is
// a recursive type, it needs to find the incomplete entry for itself in
// the map.
structCodecs[t] = c
defer func() {
if retErr != nil {
delete(structCodecs, t)
}
}()
for i := range c.byIndex {
f := t.Field(i)
name, opts := f.Tag.Get("datastore"), ""
if i := strings.Index(name, ","); i != -1 {
name, opts = name[:i], name[i+1:]
}
if name == "" {
if !f.Anonymous {
name = f.Name
}
} else if name == "-" {
c.byIndex[i] = structTag{name: name}
continue
} else if !validPropertyName(name) {
return nil, fmt.Errorf("datastore: struct tag has invalid property name: %q", name)
}
substructType, fIsSlice := reflect.Type(nil), false
switch f.Type.Kind() {
case reflect.Struct:
substructType = f.Type
case reflect.Slice:
if f.Type.Elem().Kind() == reflect.Struct {
substructType = f.Type.Elem()
}
fIsSlice = f.Type != typeOfByteSlice
c.hasSlice = c.hasSlice || fIsSlice
}
if substructType != nil && substructType != typeOfTime && substructType != typeOfGeoPoint {
if name != "" {
name = name + "."
}
sub, err := getStructCodecLocked(substructType)
if err != nil {
return nil, err
}
if !sub.complete {
return nil, fmt.Errorf("datastore: recursive struct: field %q", f.Name)
}
if fIsSlice && sub.hasSlice {
return nil, fmt.Errorf(
"datastore: flattening nested structs leads to a slice of slices: field %q", f.Name)
}
c.hasSlice = c.hasSlice || sub.hasSlice
for relName := range sub.byName {
absName := name + relName
if _, ok := c.byName[absName]; ok {
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", absName)
}
c.byName[absName] = fieldCodec{index: i, substructCodec: sub}
}
} else {
if _, ok := c.byName[name]; ok {
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", name)
}
c.byName[name] = fieldCodec{index: i}
}
c.byIndex[i] = structTag{
name: name,
noIndex: opts == "noindex",
}
}
c.complete = true
return c, nil
}
// structPLS adapts a struct to be a PropertyLoadSaver.
type structPLS struct {
v reflect.Value
codec *structCodec
}
// newStructPLS returns a PropertyLoadSaver for the struct pointer p.
func newStructPLS(p interface{}) (PropertyLoadSaver, error) {
v := reflect.ValueOf(p)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return nil, ErrInvalidEntityType
}
v = v.Elem()
codec, err := getStructCodec(v.Type())
if err != nil {
return nil, err
}
return structPLS{v, codec}, nil
}
// LoadStruct loads the properties from p to dst.
// dst must be a struct pointer.
func LoadStruct(dst interface{}, p []Property) error {
x, err := newStructPLS(dst)
if err != nil {
return err
}
return x.Load(p)
}
// SaveStruct returns the properties from src as a slice of Properties.
// src must be a struct pointer.
func SaveStruct(src interface{}) ([]Property, error) {
x, err := newStructPLS(src)
if err != nil {
return nil, err
}
return x.Save()
}
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package datastore
import (
"errors"
"fmt"
"math"
"reflect"
"time"
"github.com/golang/protobuf/proto"
"google.golang.org/appengine"
pb "google.golang.org/appengine/internal/datastore"
)
func toUnixMicro(t time.Time) int64 {
// We cannot use t.UnixNano() / 1e3 because we want to handle times more than
// 2^63 nanoseconds (which is about 292 years) away from 1970, and those cannot
// be represented in the numerator of a single int64 divide.
return t.Unix()*1e6 + int64(t.Nanosecond()/1e3)
}
func fromUnixMicro(t int64) time.Time {
return time.Unix(t/1e6, (t%1e6)*1e3)
}
var (
minTime = time.Unix(int64(math.MinInt64)/1e6, (int64(math.MinInt64)%1e6)*1e3)
maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6)*1e3)
)
// valueToProto converts a named value to a newly allocated Property.
// The returned error string is empty on success.
func valueToProto(defaultAppID, name string, v reflect.Value, multiple bool) (p *pb.Property, errStr string) {
var (
pv pb.PropertyValue
unsupported bool
)
switch v.Kind() {
case reflect.Invalid:
// No-op.
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
pv.Int64Value = proto.Int64(v.Int())
case reflect.Bool:
pv.BooleanValue = proto.Bool(v.Bool())
case reflect.String:
pv.StringValue = proto.String(v.String())
case reflect.Float32, reflect.Float64:
pv.DoubleValue = proto.Float64(v.Float())
case reflect.Ptr:
if k, ok := v.Interface().(*Key); ok {
if k != nil {
pv.Referencevalue = keyToReferenceValue(defaultAppID, k)
}
} else {
unsupported = true
}
case reflect.Struct:
switch t := v.Interface().(type) {
case time.Time:
if t.Before(minTime) || t.After(maxTime) {
return nil, "time value out of range"
}
pv.Int64Value = proto.Int64(toUnixMicro(t))
case appengine.GeoPoint:
if !t.Valid() {
return nil, "invalid GeoPoint value"
}
// NOTE: Strangely, latitude maps to X, longitude to Y.
pv.Pointvalue = &pb.PropertyValue_PointValue{X: &t.Lat, Y: &t.Lng}
default:
unsupported = true
}
case reflect.Slice:
if b, ok := v.Interface().([]byte); ok {
pv.StringValue = proto.String(string(b))
} else {
// nvToProto should already catch slice values.
// If we get here, we have a slice of slice values.
unsupported = true
}
default:
unsupported = true
}
if unsupported {
return nil, "unsupported datastore value type: " + v.Type().String()
}
p = &pb.Property{
Name: proto.String(name),
Value: &pv,
Multiple: proto.Bool(multiple),
}
if v.IsValid() {
switch v.Interface().(type) {
case []byte:
p.Meaning = pb.Property_BLOB.Enum()
case ByteString:
p.Meaning = pb.Property_BYTESTRING.Enum()
case appengine.BlobKey:
p.Meaning = pb.Property_BLOBKEY.Enum()
case time.Time:
p.Meaning = pb.Property_GD_WHEN.Enum()
case appengine.GeoPoint:
p.Meaning = pb.Property_GEORSS_POINT.Enum()
}
}
return p, ""
}
// saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
func saveEntity(defaultAppID string, key *Key, src interface{}) (*pb.EntityProto, error) {
var err error
var props []Property
if e, ok := src.(PropertyLoadSaver); ok {
props, err = e.Save()
} else {
props, err = SaveStruct(src)
}
if err != nil {
return nil, err
}
return propertiesToProto(defaultAppID, key, props)
}
func saveStructProperty(props *[]Property, name string, noIndex, multiple bool, v reflect.Value) error {
p := Property{
Name: name,
NoIndex: noIndex,
Multiple: multiple,
}
switch x := v.Interface().(type) {
case *Key:
p.Value = x
case time.Time:
p.Value = x
case appengine.BlobKey:
p.Value = x
case appengine.GeoPoint:
p.Value = x
case ByteString:
p.Value = x
default:
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p.Value = v.Int()
case reflect.Bool:
p.Value = v.Bool()
case reflect.String:
p.Value = v.String()
case reflect.Float32, reflect.Float64:
p.Value = v.Float()
case reflect.Slice:
if v.Type().Elem().Kind() == reflect.Uint8 {
p.NoIndex = true
p.Value = v.Bytes()
}
case reflect.Struct:
if !v.CanAddr() {
return fmt.Errorf("datastore: unsupported struct field: value is unaddressable")
}
sub, err := newStructPLS(v.Addr().Interface())
if err != nil {
return fmt.Errorf("datastore: unsupported struct field: %v", err)
}
return sub.(structPLS).save(props, name, noIndex, multiple)
}
}
if p.Value == nil {
return fmt.Errorf("datastore: unsupported struct field type: %v", v.Type())
}
*props = append(*props, p)
return nil
}
func (s structPLS) Save() ([]Property, error) {
var props []Property
if err := s.save(&props, "", false, false); err != nil {
return nil, err
}
return props, nil
}
func (s structPLS) save(props *[]Property, prefix string, noIndex, multiple bool) error {
for i, t := range s.codec.byIndex {
if t.name == "-" {
continue
}
name := t.name
if prefix != "" {
name = prefix + name
}
v := s.v.Field(i)
if !v.IsValid() || !v.CanSet() {
continue
}
noIndex1 := noIndex || t.noIndex
// For slice fields that aren't []byte, save each element.
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
for j := 0; j < v.Len(); j++ {
if err := saveStructProperty(props, name, noIndex1, true, v.Index(j)); err != nil {
return err
}
}
continue
}
// Otherwise, save the field itself.
if err := saveStructProperty(props, name, noIndex1, multiple, v); err != nil {
return err
}
}
return nil
}
func propertiesToProto(defaultAppID string, key *Key, props []Property) (*pb.EntityProto, error) {
e := &pb.EntityProto{
Key: keyToProto(defaultAppID, key),
}
if key.parent == nil {
e.EntityGroup = &pb.Path{}
} else {
e.EntityGroup = keyToProto(defaultAppID, key.root()).Path
}
prevMultiple := make(map[string]bool)
for _, p := range props {
if pm, ok := prevMultiple[p.Name]; ok {
if !pm || !p.Multiple {
return nil, fmt.Errorf("datastore: multiple Properties with Name %q, but Multiple is false", p.Name)
}
} else {
prevMultiple[p.Name] = p.Multiple
}
x := &pb.Property{
Name: proto.String(p.Name),
Value: new(pb.PropertyValue),
Multiple: proto.Bool(p.Multiple),
}
switch v := p.Value.(type) {
case int64:
x.Value.Int64Value = proto.Int64(v)
case bool:
x.Value.BooleanValue = proto.Bool(v)
case string:
x.Value.StringValue = proto.String(v)
if p.NoIndex {
x.Meaning = pb.Property_TEXT.Enum()
}
case float64:
x.Value.DoubleValue = proto.Float64(v)
case *Key:
if v != nil {
x.Value.Referencevalue = keyToReferenceValue(defaultAppID, v)
}
case time.Time:
if v.Before(minTime) || v.After(maxTime) {
return nil, fmt.Errorf("datastore: time value out of range")
}
x.Value.Int64Value = proto.Int64(toUnixMicro(v))
x.Meaning = pb.Property_GD_WHEN.Enum()
case appengine.BlobKey:
x.Value.StringValue = proto.String(string(v))
x.Meaning = pb.Property_BLOBKEY.Enum()
case appengine.GeoPoint:
if !v.Valid() {
return nil, fmt.Errorf("datastore: invalid GeoPoint value")
}
// NOTE: Strangely, latitude maps to X, longitude to Y.
x.Value.Pointvalue = &pb.PropertyValue_PointValue{X: &v.Lat, Y: &v.Lng}
x.Meaning = pb.Property_GEORSS_POINT.Enum()
case []byte:
x.Value.StringValue = proto.String(string(v))
x.Meaning = pb.Property_BLOB.Enum()
if !p.NoIndex {
return nil, fmt.Errorf("datastore: cannot index a []byte valued Property with Name %q", p.Name)
}
case ByteString:
x.Value.StringValue = proto.String(string(v))
x.Meaning = pb.Property_BYTESTRING.Enum()
default:
if p.Value != nil {
return nil, fmt.Errorf("datastore: invalid Value type for a Property with Name %q", p.Name)
}
}
if p.NoIndex {
e.RawProperty = append(e.RawProperty, x)
} else {
e.Property = append(e.Property, x)
if len(e.Property) > maxIndexedProperties {
return nil, errors.New("datastore: too many indexed properties")
}
}
}
return e, nil
}
// Copyright 2012 Google Inc. All Rights Reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package datastore
import (
"testing"
"time"
)
func TestUnixMicro(t *testing.T) {
// Test that all these time.Time values survive a round trip to unix micros.
testCases := []time.Time{
{},
time.Date(2, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(23, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(234, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1600, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1700, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1800, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC),
time.Unix(-1e6, -1000),
time.Unix(-1e6, 0),
time.Unix(-1e6, +1000),
time.Unix(-60, -1000),
time.Unix(-60, 0),
time.Unix(-60, +1000),
time.Unix(-1, -1000),
time.Unix(-1, 0),
time.Unix(-1, +1000),
time.Unix(0, -3000),
time.Unix(0, -2000),
time.Unix(0, -1000),
time.Unix(0, 0),
time.Unix(0, +1000),
time.Unix(0, +2000),
time.Unix(+60, -1000),
time.Unix(+60, 0),
time.Unix(+60, +1000),
time.Unix(+1e6, -1000),
time.Unix(+1e6, 0),
time.Unix(+1e6, +1000),
time.Date(1999, 12, 31, 23, 59, 59, 999000, time.UTC),
time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2006, 1, 2, 15, 4, 5, 678000, time.UTC),
time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
time.Date(3456, 1, 1, 0, 0, 0, 0, time.UTC),
}
for _, tc := range testCases {
got := fromUnixMicro(toUnixMicro(tc))
if !got.Equal(tc) {
t.Errorf("got %q, want %q", got, tc)
}
}
// Test that a time.Time that isn't an integral number of microseconds
// is not perfectly reconstructed after a round trip.
t0 := time.Unix(0, 123)
t1 := fromUnixMicro(toUnixMicro(t0))
if t1.Nanosecond()%1000 != 0 || t0.Nanosecond()%1000 == 0 {
t.Errorf("quantization to µs: got %q with %d ns, started with %d ns", t1, t1.Nanosecond(), t0.Nanosecond())
}
}
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package datastore
import (
"errors"
"github.com/golang/protobuf/proto"
"google.golang.org/appengine"
"google.golang.org/appengine/internal"
basepb "google.golang.org/appengine/internal/base"
pb "google.golang.org/appengine/internal/datastore"
)
func init() {
internal.RegisterTransactionSetter(func(x *pb.Query, t *pb.Transaction) {
x.Transaction = t
})
internal.RegisterTransactionSetter(func(x *pb.GetRequest, t *pb.Transaction) {
x.Transaction = t
})
internal.RegisterTransactionSetter(func(x *pb.PutRequest, t *pb.Transaction) {
x.Transaction = t
})
internal.RegisterTransactionSetter(func(x *pb.DeleteRequest, t *pb.Transaction) {
x.Transaction = t
})
}
// ErrConcurrentTransaction is returned when a transaction is rolled back due
// to a conflict with a concurrent transaction.
var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction")
type transaction struct {
appengine.Context
transaction pb.Transaction
finished bool
}
func (t *transaction) Call(service, method string, in, out proto.Message, opts *internal.CallOptions) error {
if t.finished {
return errors.New("datastore: transaction context has expired")
}
internal.ApplyTransaction(in, &t.transaction)
return t.Context.Call(service, method, in, out, opts)
}
func runOnce(c appengine.Context, f func(appengine.Context) error, opts *TransactionOptions) error {
// Begin the transaction.
t := &transaction{Context: c}
req := &pb.BeginTransactionRequest{
App: proto.String(c.FullyQualifiedAppID()),
}
if opts != nil && opts.XG {
req.AllowMultipleEg = proto.Bool(true)
}
if err := t.Context.Call("datastore_v3", "BeginTransaction", req, &t.transaction, nil); err != nil {
return err
}
// Call f, rolling back the transaction if f returns a non-nil error, or panics.
// The panic is not recovered.
defer func() {
if t.finished {
return
}
t.finished = true
// Ignore the error return value, since we are already returning a non-nil
// error (or we're panicking).
c.Call("datastore_v3", "Rollback", &t.transaction, &basepb.VoidProto{}, nil)
}()
if err := f(t); err != nil {
return err
}
t.finished = true
// Commit the transaction.
res := &pb.CommitResponse{}
err := c.Call("datastore_v3", "Commit", &t.transaction, res, nil)
if ae, ok := err.(*internal.APIError); ok {
if appengine.IsDevAppServer() {
// The Python Dev AppServer raises an ApplicationError with error code 2 (which is
// Error.CONCURRENT_TRANSACTION) and message "Concurrency exception.".
if ae.Code == int32(pb.Error_BAD_REQUEST) && ae.Detail == "ApplicationError: 2 Concurrency exception." {
return ErrConcurrentTransaction
}
}
if ae.Code == int32(pb.Error_CONCURRENT_TRANSACTION) {
return ErrConcurrentTransaction
}
}
return err
}
// RunInTransaction runs f in a transaction. It calls f with a transaction
// context tc that f should use for all App Engine operations.
//
// If f returns nil, RunInTransaction attempts to commit the transaction,
// returning nil if it succeeds. If the commit fails due to a conflicting
// transaction, RunInTransaction retries f, each time with a new transaction
// context. It gives up and returns ErrConcurrentTransaction after three
// failed attempts.
//
// If f returns non-nil, then any datastore changes will not be applied and
// RunInTransaction returns that same error. The function f is not retried.
//
// Note that when f returns, the transaction is not yet committed. Calling code
// must be careful not to assume that any of f's changes have been committed
// until RunInTransaction returns nil.
//
// Nested transactions are not supported; c may not be a transaction context.
func RunInTransaction(c appengine.Context, f func(tc appengine.Context) error, opts *TransactionOptions) error {
if _, ok := c.(*transaction); ok {
return errors.New("datastore: nested transactions are not supported")
}
for i := 0; i < 3; i++ {
if err := runOnce(c, f, opts); err != ErrConcurrentTransaction {
return err
}
}
return ErrConcurrentTransaction
}
// TransactionOptions are the options for running a transaction.
type TransactionOptions struct {
// XG is whether the transaction can cross multiple entity groups. In
// comparison, a single group transaction is one where all datastore keys
// used have the same root key. Note that cross group transactions do not
// have the same behavior as single group transactions. In particular, it
// is much more likely to see partially applied transactions in different
// entity groups, in global queries.
// It is valid to set XG to true even if the transaction is within a
// single entity group.
XG bool
}
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package delay provides a way to execute code outside the scope of a
user request by using the taskqueue API.
To declare a function that may be executed later, call Func
in a top-level assignment context, passing it an arbitrary string key
and a function whose first argument is of type appengine.Context.
var laterFunc = delay.Func("key", myFunc)
It is also possible to use a function literal.
var laterFunc = delay.Func("key", func(c appengine.Context, x string) {
// ...
})
To call a function, invoke its Call method.
laterFunc.Call(c, "something")
A function may be called any number of times. If the function has any
return arguments, and the last one is of type error, the function may
return a non-nil error to signal that the function should be retried.
The arguments to functions may be of any type that is encodable by the gob
package. If an argument is of interface type, it is the client's responsibility
to register with the gob package whatever concrete type may be passed for that
argument; see http://golang.org/pkg/gob/#Register for details.
Any errors during initialization or execution of a function will be
logged to the application logs. Error logs that occur during initialization will
be associated with the request that invoked the Call method.
The state of a function invocation that has not yet successfully
executed is preserved by combining the file name in which it is declared
with the string key that was passed to the Func function. Updating an app
with pending function invocations is safe as long as the relevant
functions have the (filename, key) combination preserved.
The delay package uses the Task Queue API to create tasks that call the
reserved application path "/_ah/queue/go/delay".
This path must not be marked as "login: required" in app.yaml;
it must be marked as "login: admin" or have no access restriction.
*/
package delay
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"net/http"
"reflect"
"runtime"
"google.golang.org/appengine"
"google.golang.org/appengine/taskqueue"
)
// Function represents a function that may have a delayed invocation.
type Function struct {
fv reflect.Value // Kind() == reflect.Func
key string
err error // any error during initialization
}
const (
// The HTTP path for invocations.
path = "/_ah/queue/go/delay"
// Use the default queue.
queue = ""
)
var (
// registry of all delayed functions
funcs = make(map[string]*Function)
// precomputed types
contextType = reflect.TypeOf((*appengine.Context)(nil)).Elem()
errorType = reflect.TypeOf((*error)(nil)).Elem()
// errors
errFirstArg = errors.New("first argument must be appengine.Context")
)
// Func declares a new Function. The second argument must be a function with a
// first argument of type appengine.Context.
// This function must be called at program initialization time. That means it
// must be called in a global variable declaration or from an init function.
// This restriction is necessary because the instance that delays a function
// call may not be the one that executes it. Only the code executed at program
// initialization time is guaranteed to have been run by an instance before it
// receives a request.
func Func(key string, i interface{}) *Function {
f := &Function{fv: reflect.ValueOf(i)}
// Derive unique, somewhat stable key for this func.
_, file, _, _ := runtime.Caller(1)
f.key = file + ":" + key
t := f.fv.Type()
if t.Kind() != reflect.Func {
f.err = errors.New("not a function")
return f
}
if t.NumIn() == 0 || t.In(0) != contextType {
f.err = errFirstArg
return f
}
// Register the function's arguments with the gob package.
// This is required because they are marshaled inside a []interface{}.
// gob.Register only expects to be called during initialization;
// that's fine because this function expects the same.
for i := 0; i < t.NumIn(); i++ {
// Only concrete types may be registered. If the argument has
// interface type, the client is resposible for registering the
// concrete types it will hold.
if t.In(i).Kind() == reflect.Interface {
continue
}
gob.Register(reflect.Zero(t.In(i)).Interface())
}
funcs[f.key] = f
return f
}
type invocation struct {
Key string
Args []interface{}
}
// Call invokes a delayed function.
// f.Call(c, ...)
// is equivalent to
// t, _ := f.Task(...)
// taskqueue.Add(c, t, "")
func (f *Function) Call(c appengine.Context, args ...interface{}) {
t, err := f.Task(args...)
if err != nil {
c.Errorf("%v", err)
return
}
if _, err := taskqueueAdder(c, t, queue); err != nil {
c.Errorf("delay: taskqueue.Add failed: %v", err)
return
}
}
// Task creates a Task that will invoke the function.
// Its parameters may be tweaked before adding it to a queue.
// Users should not modify the Path or Payload fields of the returned Task.
func (f *Function) Task(args ...interface{}) (*taskqueue.Task, error) {
if f.err != nil {
return nil, fmt.Errorf("delay: func is invalid: %v", f.err)
}
nArgs := len(args) + 1 // +1 for the appengine.Context
ft := f.fv.Type()
minArgs := ft.NumIn()
if ft.IsVariadic() {
minArgs--
}
if nArgs < minArgs {
return nil, fmt.Errorf("delay: too few arguments to func: %d < %d", nArgs, minArgs)
}
if !ft.IsVariadic() && nArgs > minArgs {
return nil, fmt.Errorf("delay: too many arguments to func: %d > %d", nArgs, minArgs)
}
// Check arg types.
for i := 1; i < nArgs; i++ {
at := reflect.TypeOf(args[i-1])
var dt reflect.Type
if i < minArgs {
// not a variadic arg
dt = ft.In(i)
} else {
// a variadic arg
dt = ft.In(minArgs).Elem()
}
// nil arguments won't have a type, so they need special handling.
if at == nil {
// nil interface
switch dt.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
continue // may be nil
}
return nil, fmt.Errorf("delay: argument %d has wrong type: %v is not nilable", i, dt)
}
switch at.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
av := reflect.ValueOf(args[i-1])
if av.IsNil() {
// nil value in interface; not supported by gob, so we replace it
// with a nil interface value
args[i-1] = nil
}
}
if !at.AssignableTo(dt) {
return nil, fmt.Errorf("delay: argument %d has wrong type: %v is not assignable to %v", i, at, dt)
}
}
inv := invocation{
Key: f.key,
Args: args,
}
buf := new(bytes.Buffer)
if err := gob.NewEncoder(buf).Encode(inv); err != nil {
return nil, fmt.Errorf("delay: gob encoding failed: %v", err)
}
return &taskqueue.Task{
Path: path,
Payload: buf.Bytes(),
}, nil
}
var taskqueueAdder = taskqueue.Add // for testing
func init() {
http.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
runFunc(appengine.NewContext(req), w, req)
})
}
func runFunc(c appengine.Context, w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
var inv invocation
if err := gob.NewDecoder(req.Body).Decode(&inv); err != nil {
c.Errorf("delay: failed decoding task payload: %v", err)
c.Warningf("delay: dropping task")
return
}
f := funcs[inv.Key]
if f == nil {
c.Errorf("delay: no func with key %q found", inv.Key)
c.Warningf("delay: dropping task")
return
}
ft := f.fv.Type()
in := []reflect.Value{reflect.ValueOf(c)}
for _, arg := range inv.Args {
var v reflect.Value
if arg != nil {
v = reflect.ValueOf(arg)
} else {
// Task was passed a nil argument, so we must construct
// the zero value for the argument here.
n := len(in) // we're constructing the nth argument
var at reflect.Type
if !ft.IsVariadic() || n < ft.NumIn()-1 {
at = ft.In(n)
} else {
at = ft.In(ft.NumIn() - 1).Elem()
}
v = reflect.Zero(at)
}
in = append(in, v)
}
out := f.fv.Call(in)
if n := ft.NumOut(); n > 0 && ft.Out(n-1) == errorType {
if errv := out[n-1]; !errv.IsNil() {
c.Errorf("delay: func failed (will retry): %v", errv.Interface())
w.WriteHeader(http.StatusInternalServerError)
return
}
}
}
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package delay
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"google.golang.org/appengine"
"google.golang.org/appengine/taskqueue"
)
type CustomType struct {
N int
}
type CustomInterface interface {
N() int
}
type CustomImpl int
func (c CustomImpl) N() int { return int(c) }
// CustomImpl needs to be registered with gob.
func init() {
gob.Register(CustomImpl(0))
}
var (
invalidFunc = Func("invalid", func() {})
regFuncRuns = 0
regFuncMsg = ""
regFunc = Func("reg", func(c appengine.Context, arg string) {
regFuncRuns++
regFuncMsg = arg
})
custFuncTally = 0
custFunc = Func("cust", func(c appengine.Context, ct *CustomType, ci CustomInterface) {
a, b := 2, 3
if ct != nil {
a = ct.N
}
if ci != nil {
b = ci.N()
}
custFuncTally += a + b
})
anotherCustFunc = Func("cust2", func(c appengine.Context, n int, ct *CustomType, ci CustomInterface) {
})
varFuncMsg = ""
varFunc = Func("variadic", func(c appengine.Context, format string, args ...int) {
// convert []int to []interface{} for fmt.Sprintf.
as := make([]interface{}, len(args))
for i, a := range args {
as[i] = a
}
varFuncMsg = fmt.Sprintf(format, as...)
})
errFuncRuns = 0
errFuncErr = errors.New("error!")
errFunc = Func("err", func(c appengine.Context) error {
errFuncRuns++
if errFuncRuns == 1 {
return nil
}
return errFuncErr
})
)
type fakeContext struct {
appengine.Context
logging [][]interface{}
}
func (f *fakeContext) log(level, format string, args ...interface{}) {
f.logging = append(f.logging, append([]interface{}{level, format}, args...))
}
func (f *fakeContext) Infof(format string, args ...interface{}) { f.log("INFO", format, args...) }
func (f *fakeContext) Errorf(format string, args ...interface{}) { f.log("ERROR", format, args...) }
func TestInvalidFunction(t *testing.T) {
c := &fakeContext{}
invalidFunc.Call(c)
wantLogging := [][]interface{}{
{"ERROR", "%v", fmt.Errorf("delay: func is invalid: %s", errFirstArg)},
}
if !reflect.DeepEqual(c.logging, wantLogging) {
t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
}
}
func TestVariadicFunctionArguments(t *testing.T) {
// Check the argument type validation for variadic functions.
c := &fakeContext{}
calls := 0
taskqueueAdder = func(c appengine.Context, t *taskqueue.Task, _ string) (*taskqueue.Task, error) {
calls++
return t, nil
}
varFunc.Call(c, "hi")
varFunc.Call(c, "%d", 12)
varFunc.Call(c, "%d %d %d", 3, 1, 4)
if calls != 3 {
t.Errorf("Got %d calls to taskqueueAdder, want 3", calls)
}
varFunc.Call(c, "%d %s", 12, "a string is bad")
wantLogging := [][]interface{}{
{"ERROR", "%v", errors.New("delay: argument 3 has wrong type: string is not assignable to int")},
}
if !reflect.DeepEqual(c.logging, wantLogging) {
t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
}
}
func TestBadArguments(t *testing.T) {
// Try running regFunc with different sets of inappropriate arguments.
c := &fakeContext{}
regFunc.Call(c)
regFunc.Call(c, "lala", 53)
regFunc.Call(c, 53)
wantLogging := [][]interface{}{
{"ERROR", "%v", errors.New("delay: too few arguments to func: 1 < 2")},
{"ERROR", "%v", errors.New("delay: too many arguments to func: 3 > 2")},
{"ERROR", "%v", errors.New("delay: argument 1 has wrong type: int is not assignable to string")},
}
if !reflect.DeepEqual(c.logging, wantLogging) {
t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
}
}
func TestRunningFunction(t *testing.T) {
c := &fakeContext{}
// Fake out the adding of a task.
var task *taskqueue.Task
taskqueueAdder = func(_ appengine.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
if queue != "" {
t.Errorf(`Got queue %q, expected ""`, queue)
}
task = tk
return tk, nil
}
regFuncRuns, regFuncMsg = 0, "" // reset state
const msg = "Why, hello!"
regFunc.Call(c, msg)
// Simulate the Task Queue service.
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
if err != nil {
t.Fatalf("Failed making http.Request: %v", err)
}
rw := httptest.NewRecorder()
runFunc(c, rw, req)
if regFuncRuns != 1 {
t.Errorf("regFuncRuns: got %d, want 1", regFuncRuns)
}
if regFuncMsg != msg {
t.Errorf("regFuncMsg: got %q, want %q", regFuncMsg, msg)
}
}
func TestCustomType(t *testing.T) {
c := &fakeContext{}
// Fake out the adding of a task.
var task *taskqueue.Task
taskqueueAdder = func(_ appengine.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
if queue != "" {
t.Errorf(`Got queue %q, expected ""`, queue)
}
task = tk
return tk, nil
}
custFuncTally = 0 // reset state
custFunc.Call(c, &CustomType{N: 11}, CustomImpl(13))
// Simulate the Task Queue service.
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
if err != nil {
t.Fatalf("Failed making http.Request: %v", err)
}
rw := httptest.NewRecorder()
runFunc(c, rw, req)
if custFuncTally != 24 {
t.Errorf("custFuncTally = %d, want 24", custFuncTally)
}
// Try the same, but with nil values; one is a nil pointer (and thus a non-nil interface value),
// and the other is a nil interface value.
custFuncTally = 0 // reset state
custFunc.Call(c, (*CustomType)(nil), nil)
// Simulate the Task Queue service.
req, err = http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
if err != nil {
t.Fatalf("Failed making http.Request: %v", err)
}
rw = httptest.NewRecorder()
runFunc(c, rw, req)
if custFuncTally != 5 {
t.Errorf("custFuncTally = %d, want 5", custFuncTally)
}
}
func TestRunningVariadic(t *testing.T) {
c := &fakeContext{}
// Fake out the adding of a task.
var task *taskqueue.Task
taskqueueAdder = func(_ appengine.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
if queue != "" {
t.Errorf(`Got queue %q, expected ""`, queue)
}
task = tk
return tk, nil
}
varFuncMsg = "" // reset state
varFunc.Call(c, "Amiga %d has %d KB RAM", 500, 512)
// Simulate the Task Queue service.
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
if err != nil {
t.Fatalf("Failed making http.Request: %v", err)
}
rw := httptest.NewRecorder()
runFunc(c, rw, req)
const expected = "Amiga 500 has 512 KB RAM"
if varFuncMsg != expected {
t.Errorf("varFuncMsg = %q, want %q", varFuncMsg, expected)
}
}
func TestErrorFunction(t *testing.T) {
c := &fakeContext{}
// Fake out the adding of a task.
var task *taskqueue.Task
taskqueueAdder = func(_ appengine.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
if queue != "" {
t.Errorf(`Got queue %q, expected ""`, queue)
}
task = tk
return tk, nil
}
errFunc.Call(c)
// Simulate the Task Queue service.
// The first call should succeed; the second call should fail.
{
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
if err != nil {
t.Fatalf("Failed making http.Request: %v", err)
}
rw := httptest.NewRecorder()
runFunc(c, rw, req)
}
{
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
if err != nil {
t.Fatalf("Failed making http.Request: %v", err)
}
rw := httptest.NewRecorder()
runFunc(c, rw, req)
if rw.Code != http.StatusInternalServerError {
t.Errorf("Got status code %d, want %d", rw.Code, http.StatusInternalServerError)
}
wantLogging := [][]interface{}{
{"ERROR", "delay: func failed (will retry): %v", errFuncErr},
}
if !reflect.DeepEqual(c.logging, wantLogging) {
t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
}
}
}
# Demo application for Managed VMs.
application: vm-guestbook
version: 1
runtime: go
vm: true
api_version: go1
manual_scaling:
instances: 1
handlers:
# Favicon. Without this, the browser hits this once per page view.
- url: /favicon.ico
static_files: favicon.ico
upload: favicon.ico
# Main app. All the real work is here.
- url: /.*
script: _go_app
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment