Merge pull request #25 from vishh/network_stats
Adding per container network stats
This commit is contained in:
commit
f975ff9159
|
@ -0,0 +1,23 @@
|
|||
package libcontainer
|
||||
|
||||
import (
|
||||
"github.com/docker/libcontainer/cgroups/fs"
|
||||
"github.com/docker/libcontainer/network"
|
||||
)
|
||||
|
||||
// Returns all available stats for the given container.
|
||||
func GetStats(container *Config, state *State) (*ContainerStats, error) {
|
||||
var containerStats ContainerStats
|
||||
stats, err := fs.GetStats(container.Cgroups)
|
||||
if err != nil {
|
||||
return &containerStats, err
|
||||
}
|
||||
containerStats.CgroupStats = stats
|
||||
networkStats, err := network.GetStats(&state.NetworkState)
|
||||
if err != nil {
|
||||
return &containerStats, err
|
||||
}
|
||||
containerStats.NetworkStats = networkStats
|
||||
|
||||
return &containerStats, nil
|
||||
}
|
|
@ -57,18 +57,6 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
|
|||
return -1, err
|
||||
}
|
||||
|
||||
state := &libcontainer.State{
|
||||
InitPid: command.Process.Pid,
|
||||
InitStartTime: started,
|
||||
}
|
||||
|
||||
if err := libcontainer.SaveState(dataPath, state); err != nil {
|
||||
command.Process.Kill()
|
||||
command.Wait()
|
||||
return -1, err
|
||||
}
|
||||
defer libcontainer.DeleteState(dataPath)
|
||||
|
||||
// Do this before syncing with child so that no children
|
||||
// can escape the cgroup
|
||||
cleaner, err := SetupCgroups(container, command.Process.Pid)
|
||||
|
@ -81,12 +69,26 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
|
|||
defer cleaner.Cleanup()
|
||||
}
|
||||
|
||||
if err := InitializeNetworking(container, command.Process.Pid, syncPipe); err != nil {
|
||||
var networkState network.NetworkState
|
||||
if err := InitializeNetworking(container, command.Process.Pid, syncPipe, &networkState); err != nil {
|
||||
command.Process.Kill()
|
||||
command.Wait()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
state := &libcontainer.State{
|
||||
InitPid: command.Process.Pid,
|
||||
InitStartTime: started,
|
||||
NetworkState: networkState,
|
||||
}
|
||||
|
||||
if err := libcontainer.SaveState(dataPath, state); err != nil {
|
||||
command.Process.Kill()
|
||||
command.Wait()
|
||||
return -1, err
|
||||
}
|
||||
defer libcontainer.DeleteState(dataPath)
|
||||
|
||||
// Sync with child
|
||||
syncPipe.Close()
|
||||
|
||||
|
@ -156,18 +158,17 @@ func SetupCgroups(container *libcontainer.Config, nspid int) (cgroups.ActiveCgro
|
|||
|
||||
// InitializeNetworking creates the container's network stack outside of the namespace and moves
|
||||
// interfaces into the container's net namespaces if necessary
|
||||
func InitializeNetworking(container *libcontainer.Config, nspid int, pipe *SyncPipe) error {
|
||||
context := map[string]string{}
|
||||
func InitializeNetworking(container *libcontainer.Config, nspid int, pipe *SyncPipe, networkState *network.NetworkState) error {
|
||||
for _, config := range container.Networks {
|
||||
strategy, err := network.GetStrategy(config.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := strategy.Create((*network.Network)(config), nspid, context); err != nil {
|
||||
if err := strategy.Create((*network.Network)(config), nspid, networkState); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return pipe.SendToChild(context)
|
||||
return pipe.SendToChild(networkState)
|
||||
}
|
||||
|
||||
// GetNamespaceFlags parses the container's Namespaces options to set the correct
|
||||
|
|
|
@ -40,7 +40,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn
|
|||
}
|
||||
|
||||
// We always read this as it is a way to sync with the parent as well
|
||||
context, err := syncPipe.ReadFromParent()
|
||||
networkState, err := syncPipe.ReadFromParent()
|
||||
if err != nil {
|
||||
syncPipe.Close()
|
||||
return err
|
||||
|
@ -60,7 +60,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn
|
|||
return fmt.Errorf("setctty %s", err)
|
||||
}
|
||||
}
|
||||
if err := setupNetwork(container, context); err != nil {
|
||||
if err := setupNetwork(container, networkState); err != nil {
|
||||
return fmt.Errorf("setup networking %s", err)
|
||||
}
|
||||
if err := setupRoute(container); err != nil {
|
||||
|
@ -165,14 +165,14 @@ func SetupUser(u string) error {
|
|||
// setupVethNetwork uses the Network config if it is not nil to initialize
|
||||
// the new veth interface inside the container for use by changing the name to eth0
|
||||
// setting the MTU and IP address along with the default gateway
|
||||
func setupNetwork(container *libcontainer.Config, context map[string]string) error {
|
||||
func setupNetwork(container *libcontainer.Config, networkState *network.NetworkState) error {
|
||||
for _, config := range container.Networks {
|
||||
strategy, err := network.GetStrategy(config.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err1 := strategy.Initialize((*network.Network)(config), context)
|
||||
err1 := strategy.Initialize((*network.Network)(config), networkState)
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/docker/libcontainer/network"
|
||||
)
|
||||
|
||||
// SyncPipe allows communication to and from the child processes
|
||||
|
@ -43,8 +45,8 @@ func (s *SyncPipe) Parent() *os.File {
|
|||
return s.parent
|
||||
}
|
||||
|
||||
func (s *SyncPipe) SendToChild(context map[string]string) error {
|
||||
data, err := json.Marshal(context)
|
||||
func (s *SyncPipe) SendToChild(networkState *network.NetworkState) error {
|
||||
data, err := json.Marshal(networkState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -52,18 +54,18 @@ func (s *SyncPipe) SendToChild(context map[string]string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *SyncPipe) ReadFromParent() (map[string]string, error) {
|
||||
func (s *SyncPipe) ReadFromParent() (*network.NetworkState, error) {
|
||||
data, err := ioutil.ReadAll(s.child)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading from sync pipe %s", err)
|
||||
}
|
||||
var context map[string]string
|
||||
var networkState *network.NetworkState
|
||||
if len(data) > 0 {
|
||||
if err := json.Unmarshal(data, &context); err != nil {
|
||||
if err := json.Unmarshal(data, &networkState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return context, nil
|
||||
return networkState, nil
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,11 @@ import (
|
|||
type Loopback struct {
|
||||
}
|
||||
|
||||
func (l *Loopback) Create(n *Network, nspid int, context map[string]string) error {
|
||||
func (l *Loopback) Create(n *Network, nspid int, networkState *NetworkState) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loopback) Initialize(config *Network, context map[string]string) error {
|
||||
func (l *Loopback) Initialize(config *Network, networkState *NetworkState) error {
|
||||
if err := SetMtu("lo", config.Mtu); err != nil {
|
||||
return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err)
|
||||
}
|
||||
|
|
|
@ -14,17 +14,16 @@ import (
|
|||
type NetNS struct {
|
||||
}
|
||||
|
||||
func (v *NetNS) Create(n *Network, nspid int, context map[string]string) error {
|
||||
context["nspath"] = n.NsPath
|
||||
func (v *NetNS) Create(n *Network, nspid int, networkState *NetworkState) error {
|
||||
networkState.NsPath = n.NsPath
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *NetNS) Initialize(config *Network, context map[string]string) error {
|
||||
nspath, exists := context["nspath"]
|
||||
if !exists {
|
||||
return fmt.Errorf("nspath does not exist in network context")
|
||||
func (v *NetNS) Initialize(config *Network, networkState *NetworkState) error {
|
||||
if networkState.NsPath == "" {
|
||||
return fmt.Errorf("nspath does is not specified in NetworkState")
|
||||
}
|
||||
f, err := os.OpenFile(nspath, os.O_RDONLY, 0)
|
||||
f, err := os.OpenFile(networkState.NsPath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed get network namespace fd: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type NetworkStats struct {
|
||||
RxBytes uint64 `json:"rx_bytes,omitempty"`
|
||||
RxPackets uint64 `json:"rx_packets,omitempty"`
|
||||
RxErrors uint64 `json:"rx_errors,omitempty"`
|
||||
RxDropped uint64 `json:"rx_dropped,omitempty"`
|
||||
TxBytes uint64 `json:"tx_bytes,omitempty"`
|
||||
TxPackets uint64 `json:"tx_packets,omitempty"`
|
||||
TxErrors uint64 `json:"tx_errors,omitempty"`
|
||||
TxDropped uint64 `json:"tx_dropped,omitempty"`
|
||||
}
|
||||
|
||||
// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo.
|
||||
func GetStats(networkState *NetworkState) (NetworkStats, error) {
|
||||
// This can happen if the network runtime information is missing - possible if the container was created by an old version of libcontainer.
|
||||
if networkState.VethHost == "" {
|
||||
return NetworkStats{}, nil
|
||||
}
|
||||
data, err := readSysfsNetworkStats(networkState.VethHost)
|
||||
if err != nil {
|
||||
return NetworkStats{}, err
|
||||
}
|
||||
|
||||
return NetworkStats{
|
||||
RxBytes: data["rx_bytes"],
|
||||
RxPackets: data["rx_packets"],
|
||||
RxErrors: data["rx_errors"],
|
||||
RxDropped: data["rx_dropped"],
|
||||
TxBytes: data["tx_bytes"],
|
||||
TxPackets: data["tx_packets"],
|
||||
TxErrors: data["tx_errors"],
|
||||
TxDropped: data["tx_dropped"],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Reads all the statistics available under /sys/class/net/<EthInterface>/statistics as a map with file name as key and data as integers.
|
||||
func readSysfsNetworkStats(ethInterface string) (map[string]uint64, error) {
|
||||
out := make(map[string]uint64)
|
||||
|
||||
fullPath := filepath.Join("/sys/class/net", ethInterface, "statistics/")
|
||||
err := filepath.Walk(fullPath, func(path string, _ os.FileInfo, _ error) error {
|
||||
// skip fullPath.
|
||||
if path == fullPath {
|
||||
return nil
|
||||
}
|
||||
base := filepath.Base(path)
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out[base] = value
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
}
|
|
@ -19,8 +19,8 @@ var strategies = map[string]NetworkStrategy{
|
|||
// NetworkStrategy represents a specific network configuration for
|
||||
// a container's networking stack
|
||||
type NetworkStrategy interface {
|
||||
Create(*Network, int, map[string]string) error
|
||||
Initialize(*Network, map[string]string) error
|
||||
Create(*Network, int, *NetworkState) error
|
||||
Initialize(*Network, *NetworkState) error
|
||||
}
|
||||
|
||||
// GetStrategy returns the specific network strategy for the
|
||||
|
|
|
@ -27,3 +27,14 @@ type Network struct {
|
|||
// container's interfaces if a pair is created, specifically in the case of type veth
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
}
|
||||
|
||||
// Struct describing the network specific runtime state that will be maintained by libcontainer for all running containers
|
||||
// Do not depend on it outside of libcontainer.
|
||||
type NetworkState struct {
|
||||
// The name of the veth interface on the Host.
|
||||
VethHost string `json:"veth_host,omitempty"`
|
||||
// The name of the veth interface created inside the container for the child.
|
||||
VethChild string `json:"veth_child,omitempty"`
|
||||
// Net namespace path.
|
||||
NsPath string `json:"ns_path,omitempty"`
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ type Veth struct {
|
|||
|
||||
const defaultDevice = "eth0"
|
||||
|
||||
func (v *Veth) Create(n *Network, nspid int, context map[string]string) error {
|
||||
func (v *Veth) Create(n *Network, nspid int, networkState *NetworkState) error {
|
||||
var (
|
||||
bridge = n.Bridge
|
||||
prefix = n.VethPrefix
|
||||
|
@ -31,8 +31,6 @@ func (v *Veth) Create(n *Network, nspid int, context map[string]string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
context["veth-host"] = name1
|
||||
context["veth-child"] = name2
|
||||
if err := SetInterfaceMaster(name1, bridge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -45,16 +43,16 @@ func (v *Veth) Create(n *Network, nspid int, context map[string]string) error {
|
|||
if err := SetInterfaceInNamespacePid(name2, nspid); err != nil {
|
||||
return err
|
||||
}
|
||||
networkState.VethHost = name1
|
||||
networkState.VethChild = name2
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Veth) Initialize(config *Network, context map[string]string) error {
|
||||
var (
|
||||
vethChild string
|
||||
exists bool
|
||||
)
|
||||
if vethChild, exists = context["veth-child"]; !exists {
|
||||
return fmt.Errorf("vethChild does not exist in network context")
|
||||
func (v *Veth) Initialize(config *Network, networkState *NetworkState) error {
|
||||
var vethChild = networkState.VethChild
|
||||
if vethChild == "" {
|
||||
return fmt.Errorf("vethChild is not specified")
|
||||
}
|
||||
if err := InterfaceDown(vethChild); err != nil {
|
||||
return fmt.Errorf("interface down %s %s", vethChild, err)
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/cgroups/fs"
|
||||
)
|
||||
|
||||
var statsCommand = cli.Command{
|
||||
|
@ -22,7 +21,12 @@ func statsAction(context *cli.Context) {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
stats, err := getContainerStats(container)
|
||||
runtimeCkpt, err := libcontainer.GetState(dataPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
stats, err := getStats(container, runtimeCkpt)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get stats - %v\n", err)
|
||||
}
|
||||
|
@ -31,8 +35,8 @@ func statsAction(context *cli.Context) {
|
|||
}
|
||||
|
||||
// returns the container stats in json format.
|
||||
func getContainerStats(container *libcontainer.Config) (string, error) {
|
||||
stats, err := fs.GetStats(container.Cgroups)
|
||||
func getStats(container *libcontainer.Config, state *libcontainer.State) (string, error) {
|
||||
stats, err := libcontainer.GetStats(container, state)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
13
state.go
13
state.go
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/libcontainer/network"
|
||||
)
|
||||
|
||||
// State represents a running container's state
|
||||
|
@ -12,12 +14,17 @@ type State struct {
|
|||
InitPid int `json:"init_pid,omitempty"`
|
||||
// InitStartTime is the init process start time
|
||||
InitStartTime string `json:"init_start_time,omitempty"`
|
||||
// Network runtime state.
|
||||
NetworkState network.NetworkState `json:"network_state,omitempty"`
|
||||
}
|
||||
|
||||
// The name of the runtime state file
|
||||
const stateFile = "state.json"
|
||||
|
||||
// SaveState writes the container's runtime state to a state.json file
|
||||
// in the specified path
|
||||
func SaveState(basePath string, state *State) error {
|
||||
f, err := os.Create(filepath.Join(basePath, "state.json"))
|
||||
f, err := os.Create(filepath.Join(basePath, stateFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -28,7 +35,7 @@ func SaveState(basePath string, state *State) error {
|
|||
|
||||
// GetState reads the state.json file for a running container
|
||||
func GetState(basePath string) (*State, error) {
|
||||
f, err := os.Open(filepath.Join(basePath, "state.json"))
|
||||
f, err := os.Open(filepath.Join(basePath, stateFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -44,5 +51,5 @@ func GetState(basePath string) (*State, error) {
|
|||
|
||||
// DeleteState deletes the state.json file
|
||||
func DeleteState(basePath string) error {
|
||||
return os.Remove(filepath.Join(basePath, "state.json"))
|
||||
return os.Remove(filepath.Join(basePath, stateFile))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package libcontainer
|
||||
|
||||
import (
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
"github.com/docker/libcontainer/network"
|
||||
)
|
||||
|
||||
type ContainerStats struct {
|
||||
NetworkStats network.NetworkStats `json:"network_stats, omitempty"`
|
||||
CgroupStats *cgroups.Stats `json:"cgroup_stats, omitempty"`
|
||||
}
|
Loading…
Reference in New Issue