The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/3902
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === This adds a resource API for a LXD server. Sending a request will currently yield cpu and memory information. Further resources for appropriate LXD objects might be added later. Signed-off-by: Christian Brauner <[email protected]>
From 315b39caa69ace1f08938f76ceca95f6c4834b17 Mon Sep 17 00:00:00 2001 From: Christian Brauner <[email protected]> Date: Thu, 5 Oct 2017 15:02:06 +0200 Subject: [PATCH 1/6] resources: add api structs Signed-off-by: Christian Brauner <[email protected]> --- shared/api/resource.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 shared/api/resource.go diff --git a/shared/api/resource.go b/shared/api/resource.go new file mode 100644 index 000000000..20e8ceb20 --- /dev/null +++ b/shared/api/resource.go @@ -0,0 +1,29 @@ +package api + +// Resources represents the system resources avaible for LXD +type Resources struct { + CPU CPU `json:"cpu" yaml:"cpu"` + Memory Memory `json:"memory" yaml:"memory"` +} + +// Socket represents a cpu socket on the system +type Socket struct { + Cores uint64 `json:"cores" yaml:"cores"` + Frequency uint64 `json:"frequency,omitempty" yaml:"frequency,omitempty"` + FrequencyTurbo uint64 `json:"frequency_turbo,omitempty" yaml:"Frequency_turbo,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Vendor string `json:"vendor,omitempty" yaml:"vendor,omitempty"` + Threads uint64 `json:"threads" yaml:"threads"` +} + +// CPU represents the cpu resources available on the system +type CPU struct { + Sockets []Socket `json:"sockets" yaml:"sockets"` + Total uint64 `json:"total" yaml:"total"` +} + +// Memory represents the memory resources available on the system +type Memory struct { + Used uint64 `json:"used" yaml:"used"` + Total uint64 `json:"total" yaml:"total"` +} From a5cefef7503a6591f7bdeaa08097ef46321d37aa Mon Sep 17 00:00:00 2001 From: Christian Brauner <[email protected]> Date: Thu, 5 Oct 2017 15:55:42 +0200 Subject: [PATCH 2/6] client: add GetServerResources() Signed-off-by: Christian Brauner <[email protected]> --- client/interfaces.go | 1 + client/lxd_server.go | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/client/interfaces.go b/client/interfaces.go index f6cb6006c..7905dcfd3 100644 --- a/client/interfaces.go +++ b/client/interfaces.go @@ -43,6 +43,7 @@ type ContainerServer interface { // Server functions GetServer() (server *api.Server, ETag string, err error) + GetServerResources() (*api.Resources, error) UpdateServer(server api.ServerPut, ETag string) (err error) HasExtension(extension string) bool diff --git a/client/lxd_server.go b/client/lxd_server.go index 2e42f3944..6f50a2cc8 100644 --- a/client/lxd_server.go +++ b/client/lxd_server.go @@ -1,6 +1,8 @@ package lxd import ( + "fmt" + "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" ) @@ -53,3 +55,20 @@ func (r *ProtocolLXD) HasExtension(extension string) bool { return false } + +// GetServerResources returns the resources available to a given LXD server +func (r *ProtocolLXD) GetServerResources() (*api.Resources, error) { + if !r.HasExtension("resources") { + return nil, fmt.Errorf("The server is missing the required \"resources\" API extension") + } + + resources := api.Resources{} + + // Fetch the raw value + _, err := r.queryStruct("GET", "/resources", nil, "", &resources) + if err != nil { + return nil, err + } + + return &resources, nil +} From 2bd515ce8b0399cd46fca96ad4a1c16af1a30edf Mon Sep 17 00:00:00 2001 From: Christian Brauner <[email protected]> Date: Thu, 5 Oct 2017 15:02:21 +0200 Subject: [PATCH 3/6] resources: add helpers Signed-off-by: Christian Brauner <[email protected]> --- lxd/resources_utils.go | 339 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 lxd/resources_utils.go diff --git a/lxd/resources_utils.go b/lxd/resources_utils.go new file mode 100644 index 000000000..9acb6413d --- /dev/null +++ b/lxd/resources_utils.go @@ -0,0 +1,339 @@ +package main + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "strconv" + "strings" + + "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/shared/api" +) + +type thread struct { + ID uint64 + vendor string + name string + coreID uint64 + socketID uint64 + frequency uint64 + frequencyTurbo uint64 +} + +func parseNumberFromFile(file string) (int64, error) { + buf, err := ioutil.ReadFile(file) + if err != nil { + return int64(0), err + } + + str := strings.TrimSpace(string(buf)) + nr, err := strconv.Atoi(str) + if err != nil { + return int64(0), err + } + + return int64(nr), nil +} + +func parseCpuinfo() ([]thread, error) { + f, err := os.Open("/proc/cpuinfo") + if err != nil { + return nil, err + } + defer f.Close() + + threads := []thread{} + scanner := bufio.NewScanner(f) + var t *thread + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "processor") { + i := strings.Index(line, ":") + if i < 0 { + return nil, err + } + i++ + + line = line[i:] + line = strings.TrimSpace(line) + + id, err := strconv.Atoi(line) + if err != nil { + return nil, err + } + + t = &thread{} + t.ID = uint64(id) + + path := fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/core_id", t.ID) + coreID, err := parseNumberFromFile(path) + if err != nil { + return nil, err + } + + t.coreID = uint64(coreID) + + path = fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/physical_package_id", t.ID) + sockID, err := parseNumberFromFile(path) + if err != nil { + return nil, err + } + + t.socketID = uint64(sockID) + + path = fmt.Sprintf("/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", t.ID) + maxFreq, err := parseNumberFromFile(path) + if err != nil { + return nil, err + } + + t.frequencyTurbo = uint64(maxFreq / 1000) + + threads = append(threads, *t) + } else if strings.HasPrefix(line, "vendor_id") { + i := strings.Index(line, ":") + if i < 0 { + return nil, err + } + i++ + + line = line[i:] + line = strings.TrimSpace(line) + + if t != nil { + threads[len(threads)-1].name = line + } + } else if strings.HasPrefix(line, "model name") { + i := strings.Index(line, ":") + if i < 0 { + return nil, err + } + i++ + + line = line[i:] + line = strings.TrimSpace(line) + + if t != nil { + threads[len(threads)-1].vendor = line + } + } else if strings.HasPrefix(line, "cpu MHz") { + i := strings.Index(line, ":") + if i < 0 { + return nil, err + } + i++ + + line = line[i:] + line = strings.TrimSpace(line) + + if t != nil { + tmp, err := strconv.ParseFloat(line, 64) + if err != nil { + return nil, err + } + + threads[len(threads)-1].frequency = uint64(tmp) + } + } + } + + if len(threads) == 0 { + return nil, db.NoSuchObjectError + } + + return threads, err +} + +func parseSysDevSystemCpu() ([]thread, error) { + ents, err := ioutil.ReadDir("/sys/devices/system/cpu/") + if err != nil { + return nil, err + } + + threads := []thread{} + for _, ent := range ents { + entName := ent.Name() + if !strings.HasPrefix(entName, "cpu") { + continue + } + + entName = entName[len("cpu"):] + idx, err := strconv.Atoi(entName) + if err != nil { + continue + } + + t := thread{} + t.ID = uint64(idx) + path := fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/core_id", t.ID) + coreID, err := parseNumberFromFile(path) + if err != nil { + return nil, err + } + + t.coreID = uint64(coreID) + + path = fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/physical_package_id", t.ID) + sockID, err := parseNumberFromFile(path) + if err != nil { + return nil, err + } + + t.socketID = uint64(sockID) + + path = fmt.Sprintf("/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", t.ID) + maxFreq, err := parseNumberFromFile(path) + if err != nil { + return nil, err + } + + t.frequencyTurbo = uint64(maxFreq / 1000) + + threads = append(threads, t) + } + + if len(threads) == 0 { + return nil, db.NoSuchObjectError + } + + return threads, err +} + +func getThreads() ([]thread, error) { + threads, err := parseCpuinfo() + if err == nil { + return threads, nil + } + + threads, err = parseSysDevSystemCpu() + if err != nil { + return nil, err + } + + return threads, nil +} + +func cpuResource() (api.CPU, error) { + c := api.CPU{} + + threads, err := getThreads() + if err != nil { + return c, err + } + + var cur *api.Socket + c.Total = uint64(len(threads)) + c.Sockets = append(c.Sockets, api.Socket{}) + for _, v := range threads { + if uint64(len(c.Sockets)) <= v.socketID { + c.Sockets = append(c.Sockets, api.Socket{}) + cur = &c.Sockets[v.socketID] + } else { + cur = &c.Sockets[v.socketID] + } + + if v.coreID+1 > cur.Cores { + cur.Cores++ + } + + cur.Threads++ + cur.Name = v.name + cur.Vendor = v.vendor + cur.Frequency = v.frequency + cur.FrequencyTurbo = v.frequencyTurbo + } + + return c, nil +} + +func memoryResource() (api.Memory, error) { + var buffers uint64 + var cached uint64 + var free uint64 + var total uint64 + + mem := api.Memory{} + f, err := os.Open("/proc/meminfo") + if err != nil { + return mem, err + } + defer f.Close() + + cleanLine := func(l string) (string, error) { + l = strings.TrimSpace(l) + idx := strings.LastIndex(l, "kB") + if idx < 0 { + return "", fmt.Errorf(`Failed to detect "kB" suffix`) + } + + return strings.TrimSpace(l[:idx]), nil + } + + scanner := bufio.NewScanner(f) + found := 0 + for scanner.Scan() { + var err error + line := scanner.Text() + + if strings.HasPrefix(line, "MemTotal:") { + line, err = cleanLine(line[len("MemTotal:"):]) + if err != nil { + return mem, err + } + + total, err = strconv.ParseUint(line, 10, 64) + if err != nil { + return mem, err + } + + found++ + } else if strings.HasPrefix(line, "MemFree:") { + line, err = cleanLine(line[len("MemFree:"):]) + if err != nil { + return mem, err + } + + free, err = strconv.ParseUint(line, 10, 64) + if err != nil { + return mem, err + } + + found++ + } else if strings.HasPrefix(line, "Cached:") { + line, err = cleanLine(line[len("Cached:"):]) + if err != nil { + return mem, err + } + + cached, err = strconv.ParseUint(line, 10, 64) + if err != nil { + return mem, err + } + + found++ + } else if strings.HasPrefix(line, "Buffers:") { + line, err = cleanLine(line[len("Buffers:"):]) + if err != nil { + return mem, err + } + + buffers, err = strconv.ParseUint(line, 10, 64) + if err != nil { + return mem, err + } + + found++ + } + + if found == 4 { + break + } + } + + mem.Total = total * 1024 + mem.Used = (total - free - cached - buffers) * 1024 + + return mem, err +} From 3b6085409968791d53a03da118ed07456dadad07 Mon Sep 17 00:00:00 2001 From: Christian Brauner <[email protected]> Date: Thu, 5 Oct 2017 15:58:25 +0200 Subject: [PATCH 4/6] resources: add api endpoints Signed-off-by: Christian Brauner <[email protected]> --- lxd/resources.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lxd/resources.go diff --git a/lxd/resources.go b/lxd/resources.go new file mode 100644 index 000000000..5ca8c4f79 --- /dev/null +++ b/lxd/resources.go @@ -0,0 +1,30 @@ +package main + +import ( + "net/http" + + "github.com/lxc/lxd/shared/api" +) + +// /1.0/resources +// Get system resources +func serverResourcesGet(d *Daemon, r *http.Request) Response { + res := api.Resources{} + + cpu, err := cpuResource() + if err != nil { + return SmartError(err) + } + + mem, err := memoryResource() + if err != nil { + return SmartError(err) + } + + res.CPU = cpu + res.Memory = mem + + return SyncResponse(true, res) +} + +var serverResourceCmd = Command{name: "resources", get: serverResourcesGet} From 7e867103fefdf608e745d489525a7f9916ead28a Mon Sep 17 00:00:00 2001 From: Christian Brauner <[email protected]> Date: Thu, 5 Oct 2017 15:58:51 +0200 Subject: [PATCH 5/6] lxc: add "resources" flag to lxc info Signed-off-by: Christian Brauner <[email protected]> --- lxc/info.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lxc/info.go b/lxc/info.go index 2d3a7c989..ab97f14df 100644 --- a/lxc/info.go +++ b/lxc/info.go @@ -15,7 +15,8 @@ import ( ) type infoCmd struct { - showLog bool + showLog bool + resources bool } func (c *infoCmd) showByDefault() bool { @@ -37,6 +38,7 @@ lxc info [<remote>:] func (c *infoCmd) flags() { gnuflag.BoolVar(&c.showLog, "show-log", false, i18n.G("Show the container's last 100 log lines?")) + gnuflag.BoolVar(&c.resources, "resources", false, i18n.G("Show the resources available to the server")) } func (c *infoCmd) run(conf *config.Config, args []string) error { @@ -68,6 +70,22 @@ func (c *infoCmd) run(conf *config.Config, args []string) error { } func (c *infoCmd) remoteInfo(d lxd.ContainerServer) error { + if c.resources { + resources, err := d.GetServerResources() + if err != nil { + return err + } + + resourceData, err := yaml.Marshal(&resources) + if err != nil { + return err + } + + fmt.Printf("%s", resourceData) + + return nil + } + serverStatus, _, err := d.GetServer() if err != nil { return err From e4e858ae714259a2734b3e90b825b19147f2a435 Mon Sep 17 00:00:00 2001 From: Christian Brauner <[email protected]> Date: Thu, 5 Oct 2017 15:59:29 +0200 Subject: [PATCH 6/6] api: add "resources" extension Signed-off-by: Christian Brauner <[email protected]> --- doc/api-extensions.md | 4 ++++ doc/rest-api.md | 37 +++++++++++++++++++++++++++++++++++++ lxd/api_1.0.go | 2 ++ 3 files changed, 43 insertions(+) diff --git a/doc/api-extensions.md b/doc/api-extensions.md index 92cc342cc..02436429f 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -339,3 +339,7 @@ use by another LXD instance. ## storage\_block\_filesystem\_btrfs This adds support for btrfs as a storage volume filesystem, in addition to ext4 and xfs. + +## resources +This adds support for querying an LXD daemon for the system resources it has +available. diff --git a/doc/rest-api.md b/doc/rest-api.md index 278237d31..d7c7b3a46 100644 --- a/doc/rest-api.md +++ b/doc/rest-api.md @@ -209,6 +209,7 @@ won't work and PUT needs to be used instead. * `/1.0/operations/<uuid>/websocket` * `/1.0/profiles` * `/1.0/profiles/<name>` + * `/1.0/resources` # API details ## `/` @@ -2337,3 +2338,39 @@ Input (none at present): { } + +## `/1.0/resources` +### GET + * Description: information about the resources available to the LXD server + * Introduced: with API extension `resources` + * Authentication: guest, untrusted or trusted + * Operation: sync + * Return: dict representing the system resources + + { + "type": "sync", + "status": "Success", + "status_code": 200, + "operation": "", + "error_code": 0, + "error": "", + "metadata": { + "cpu": { + "sockets": [ + { + "cores": 2, + "frequency": 2691, + "frequency_turbo": 3400, + "name": "GenuineIntel", + "vendor": "Intel(R) Core(TM) i5-3340M CPU @ 2.70GHz", + "threads": 4 + } + ], + "total": 4 + }, + "memory": { + "used": 4454240256, + "total": 8271765504 + } + } + } diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go index 0dd41bfaa..58d613217 100644 --- a/lxd/api_1.0.go +++ b/lxd/api_1.0.go @@ -53,6 +53,7 @@ var api10 = []Command{ storagePoolVolumesCmd, storagePoolVolumesTypeCmd, storagePoolVolumeTypeCmd, + serverResourceCmd, } func api10Get(d *Daemon, r *http.Request) Response { @@ -125,6 +126,7 @@ func api10Get(d *Daemon, r *http.Request) Response { "storage_volatile_initial_source", "storage_ceph_force_osd_reuse", "storage_block_filesystem_btrfs", + "resources", }, APIStatus: "stable", APIVersion: version.APIVersion,
_______________________________________________ lxc-devel mailing list [email protected] http://lists.linuxcontainers.org/listinfo/lxc-devel
