From 712f9fb32fc0b1fae1b86b3d791fc77b69d51b95 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Mon, 9 Jun 2014 12:12:07 -0700 Subject: [PATCH] Migrating cAdvisor code from lmctfy --- .gitignore | 1 + AUTHORS | 11 + CONTRIBUTORS | 11 + LICENSE | 190 +++ TODO.txt | 2 + api/handler.go | 88 ++ cadvisor.go | 98 ++ client/client.go | 96 ++ client/client_test.go | 220 +++ container/container.go | 46 + container/docker/factory.go | 104 ++ container/docker/handler.go | 319 ++++ container/factory.go | 129 ++ container/factory_test.go | 76 + container/filter.go | 90 ++ container/filter_test.go | 123 ++ container/lmctfy/factory.go | 52 + container/lmctfy/lmctfy.pb.go | 2103 ++++++++++++++++++++++++++ container/lmctfy/lmctfy_container.go | 195 +++ container/lmctfy/virtual_host.pb.go | 273 ++++ container/samplermngr.go | 101 ++ container/statssum.go | 120 ++ container/statssum_test.go | 98 ++ container/statssumfactory.go | 48 + info/container.go | 231 +++ info/container_test.go | 86 ++ info/machine.go | 27 + manager/container.go | 176 +++ manager/machine.go | 61 + manager/manager.go | 247 +++ pages/containers.go | 199 +++ pages/containers_html.go | 160 ++ pages/static/containers_css.go | 47 + pages/static/containers_js.go | 288 ++++ pages/static/static.go | 46 + quickstart/Dockerfile | 14 + sampling/autofilter.go | 51 + sampling/autoreset.go | 60 + sampling/chainsample.go | 157 ++ sampling/chainsample_test.go | 29 + sampling/doc.go | 17 + sampling/es.go | 143 ++ sampling/es_test.go | 81 + sampling/reservoir.go | 99 ++ sampling/reservoir_test.go | 70 + sampling/sampler.go | 42 + 46 files changed, 6925 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 CONTRIBUTORS create mode 100644 LICENSE create mode 100644 TODO.txt create mode 100644 api/handler.go create mode 100644 cadvisor.go create mode 100644 client/client.go create mode 100644 client/client_test.go create mode 100644 container/container.go create mode 100644 container/docker/factory.go create mode 100644 container/docker/handler.go create mode 100644 container/factory.go create mode 100644 container/factory_test.go create mode 100644 container/filter.go create mode 100644 container/filter_test.go create mode 100644 container/lmctfy/factory.go create mode 100644 container/lmctfy/lmctfy.pb.go create mode 100644 container/lmctfy/lmctfy_container.go create mode 100644 container/lmctfy/virtual_host.pb.go create mode 100644 container/samplermngr.go create mode 100644 container/statssum.go create mode 100644 container/statssum_test.go create mode 100644 container/statssumfactory.go create mode 100644 info/container.go create mode 100644 info/container_test.go create mode 100644 info/machine.go create mode 100644 manager/container.go create mode 100644 manager/machine.go create mode 100644 manager/manager.go create mode 100644 pages/containers.go create mode 100644 pages/containers_html.go create mode 100644 pages/static/containers_css.go create mode 100644 pages/static/containers_js.go create mode 100644 pages/static/static.go create mode 100644 quickstart/Dockerfile create mode 100644 sampling/autofilter.go create mode 100644 sampling/autoreset.go create mode 100644 sampling/chainsample.go create mode 100644 sampling/chainsample_test.go create mode 100644 sampling/doc.go create mode 100644 sampling/es.go create mode 100644 sampling/es_test.go create mode 100644 sampling/reservoir.go create mode 100644 sampling/reservoir_test.go create mode 100644 sampling/sampler.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1377554e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..f4aa3d13 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,11 @@ +# This is the official list of cAdvisor authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as +# Name or Organization +# The email address is not required for organizations. + +# Please keep the list sorted. + +Google Inc. diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 00000000..d0c97f1a --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,11 @@ +# This is the official list of people who have contributed to the project. The +# copyright is held by those individuals or organizations in the AUTHORS file. +# +# Names should be added to this file like so: +# Name + +# Please keep the list sorted by first name. + +Kamil Yurtsever +Nan Deng +Victor Marmol diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..97cec18e --- /dev/null +++ b/LICENSE @@ -0,0 +1,190 @@ + Copyright 2014 The cAdvisor Authors + + 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. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 00000000..b8a6815e --- /dev/null +++ b/TODO.txt @@ -0,0 +1,2 @@ +- Allow us to have different tracking policies: only top-level, only specified, all containers +- Add ability to checkpoint state (and plugable medium for this) diff --git a/api/handler.go b/api/handler.go new file mode 100644 index 00000000..409dc917 --- /dev/null +++ b/api/handler.go @@ -0,0 +1,88 @@ +// Copyright 2014 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. + +// Handler for /api/ + +package api + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "net/url" + "strings" + "time" + + "github.com/google/cadvisor/manager" +) + +const ( + ApiResource = "/api/v1.0/" + ContainersApi = "containers" + MachineApi = "machine" +) + +func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error { + start := time.Now() + + // Get API request type. + requestType := u.Path[len(ApiResource):] + i := strings.Index(requestType, "/") + requestArgs := "" + if i != -1 { + requestArgs = requestType[i:] + requestType = requestType[:i] + } + + if requestType == MachineApi { + log.Printf("Api - Machine") + + // Get the MachineInfo + machineInfo, err := m.GetMachineInfo() + if err != nil { + return err + } + + out, err := json.Marshal(machineInfo) + if err != nil { + fmt.Fprintf(w, "Failed to marshall MachineInfo with error: %s", err) + } + w.Write(out) + } else if requestType == ContainersApi { + // The container name is the path after the requestType + containerName := requestArgs + + log.Printf("Api - Container(%s)", containerName) + + // Get the container. + cont, err := m.GetContainerInfo(containerName) + if err != nil { + fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err) + return err + } + + // Only output the container as JSON. + out, err := json.Marshal(cont) + if err != nil { + fmt.Fprintf(w, "Failed to marshall container %q with error: %s", containerName, err) + } + w.Write(out) + } else { + return fmt.Errorf("unknown API request type %q", requestType) + } + + log.Printf("Request took %s", time.Since(start)) + return nil +} diff --git a/cadvisor.go b/cadvisor.go new file mode 100644 index 00000000..352fd794 --- /dev/null +++ b/cadvisor.go @@ -0,0 +1,98 @@ +// Copyright 2014 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 main + +import ( + "flag" + "fmt" + "log" + "net/http" + "time" + + "github.com/google/cadvisor/api" + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/docker" + "github.com/google/cadvisor/container/lmctfy" + "github.com/google/cadvisor/manager" + "github.com/google/cadvisor/pages" + "github.com/google/cadvisor/pages/static" +) + +var argPort = flag.Int("port", 8080, "port to listen") +var argSampleSize = flag.Int("samples", 1024, "number of samples we want to keep") +var argResetPeriod = flag.Duration("reset_period", 2*time.Hour, "period to reset the samples") + +func main() { + flag.Parse() + // XXX(dengnan): Should we allow users to specify which sampler they want to use? + container.SetStatsParameter(&container.StatsParameter{ + Sampler: "uniform", + NumSamples: *argSampleSize, + ResetPeriod: *argResetPeriod, + }) + containerManager, err := manager.New() + if err != nil { + log.Fatalf("Failed to create a Container Manager: %s", err) + } + + if err := lmctfy.Register("/"); err != nil { + log.Printf("lmctfy registration failed: %v.", err) + log.Print("Running in docker only mode.") + if err := docker.Register(containerManager, "/"); err != nil { + log.Printf("Docker registration failed: %v.", err) + log.Fatalf("Unable to continue without docker or lmctfy.") + } + } + + if err := docker.Register(containerManager, "/docker"); err != nil { + // Ignore this error because we should work with lmctfy only + log.Printf("Docker registration failed: %v.", err) + log.Print("Running in lmctfy only mode.") + } + + // Handler for static content. + http.HandleFunc(static.StaticResource, func(w http.ResponseWriter, r *http.Request) { + err := static.HandleRequest(w, r.URL) + if err != nil { + fmt.Fprintf(w, "%s", err) + } + }) + + // Handler for the API. + http.HandleFunc(api.ApiResource, func(w http.ResponseWriter, r *http.Request) { + err := api.HandleRequest(containerManager, w, r.URL) + if err != nil { + fmt.Fprintf(w, "%s", err) + } + }) + + // Redirect / to containers page. + http.Handle("/", http.RedirectHandler(pages.ContainersPage, http.StatusTemporaryRedirect)) + + // Register the handler for the containers page. + http.HandleFunc(pages.ContainersPage, func(w http.ResponseWriter, r *http.Request) { + err := pages.ServerContainersPage(containerManager, w, r.URL) + if err != nil { + fmt.Fprintf(w, "%s", err) + } + }) + + go containerManager.Start() + + log.Print("About to serve on port ", *argPort) + + addr := fmt.Sprintf(":%v", *argPort) + log.Fatal(http.ListenAndServe(addr, nil)) +} diff --git a/client/client.go b/client/client.go new file mode 100644 index 00000000..d521b1e3 --- /dev/null +++ b/client/client.go @@ -0,0 +1,96 @@ +// Copyright 2014 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 cadvisor + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/google/cadvisor/info" +) + +type Client struct { + baseUrl string +} + +func NewClient(URL string) (*Client, error) { + c := &Client{ + baseUrl: strings.Join([]string{ + URL, + "api/v1.0", + }, "/"), + } + _, err := c.MachineInfo() + if err != nil { + return nil, err + } + return c, nil +} + +func (self *Client) machineInfoUrl() string { + return strings.Join([]string{self.baseUrl, "machine"}, "/") +} + +func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) { + u := self.machineInfoUrl() + ret := new(info.MachineInfo) + err = self.httpGetJsonData(ret, u, "machine info") + if err != nil { + return + } + minfo = ret + return +} + +func (self *Client) containerInfoUrl(name string) string { + if name[0] == '/' { + name = name[1:] + } + return strings.Join([]string{self.baseUrl, "containers", name}, "/") +} + +func (self *Client) httpGetJsonData(data interface{}, url, infoName string) error { + resp, err := http.Get(url) + if err != nil { + err = fmt.Errorf("unable to get %v: %v", infoName, err) + return err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + err = fmt.Errorf("unable to read all %v: %v", infoName, err) + return err + } + err = json.Unmarshal(body, data) + if err != nil { + err = fmt.Errorf("unable to unmarshal %v (%v): %v", infoName, string(body), err) + return err + } + return nil +} + +func (self *Client) ContainerInfo(name string) (cinfo *info.ContainerInfo, err error) { + u := self.containerInfoUrl(name) + ret := new(info.ContainerInfo) + err = self.httpGetJsonData(ret, u, fmt.Sprintf("container info for %v", name)) + if err != nil { + return + } + cinfo = ret + return +} diff --git a/client/client_test.go b/client/client_test.go new file mode 100644 index 00000000..1e4c3d62 --- /dev/null +++ b/client/client_test.go @@ -0,0 +1,220 @@ +// Copyright 2014 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 cadvisor + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/google/cadvisor/info" +) + +func testGetJsonData( + strRep string, + emptyData interface{}, + f func() (interface{}, error), +) error { + err := json.Unmarshal([]byte(strRep), emptyData) + if err != nil { + return fmt.Errorf("invalid json input: %v", err) + } + reply, err := f() + if err != nil { + return fmt.Errorf("unable to retrieve data: %v", err) + } + if !reflect.DeepEqual(reply, emptyData) { + return fmt.Errorf("retrieved wrong data: %+v != %+v", reply, emptyData) + } + return nil +} + +func cadvisorTestClient(path, reply string) (*Client, *httptest.Server, error) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == path { + fmt.Fprint(w, reply) + } else if r.URL.Path == "/api/v1.0/machine" { + fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`) + } else { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, "Page not found.") + } + })) + client, err := NewClient(ts.URL) + if err != nil { + ts.Close() + return nil, nil, err + } + return client, ts, err +} + +func TestGetMachineinfo(t *testing.T) { + respStr := `{"num_cores":8,"memory_capacity":31625871360}` + client, server, err := cadvisorTestClient("/api/v1.0/machine", respStr) + if err != nil { + t.Fatalf("unable to get a client %v", err) + } + defer server.Close() + err = testGetJsonData(respStr, &info.MachineInfo{}, func() (interface{}, error) { + return client.MachineInfo() + }) + if err != nil { + t.Fatal(err) + } +} + +func TestGetContainerInfo(t *testing.T) { + respStr := ` +{ + "name": "%v", + "subcontainers": [ + "%v/cadvisor", + "%v/docker" + ], + "spec": { + "cpu": { + "limit": 1000, + "max_limit": 18446744073709551000, + "mask": { + "data": [ + 255 + ] + } + }, + "memory": { + "limit": 9223372036854776000, + "reservation": 9223372036854776000 + } + }, + "stats": [ + { + "timestamp": "2014-06-02T19:04:37.112952602Z", + "cpu": { + "usage": { + "total": 39238585820907, + "per_cpu": [ + 6601830440753, + 5399612248434, + 4705072360704, + 4516301677099, + 4585779804509, + 4556599077540, + 4479432306284, + 4393957905584 + ], + "user": 14513300000000, + "system": 14484560000000 + }, + "load": 0 + }, + "memory": { + "limit": 9223372036854776000, + "usage": 837984256, + "container_data": { + "pgfault": 71601418, + "pgmajfault": 664 + }, + "hierarchical_data": { + "pgfault": 100135740, + "pgmajfault": 1454 + } + } + }, + { + "timestamp": "2014-06-02T19:04:38.117962404Z", + "cpu": { + "usage": { + "total": 39238651066794, + "per_cpu": [ + 6601838963405, + 5399619233761, + 4705084549250, + 4516308300389, + 4585786473026, + 4556607159034, + 4479432477487, + 4393973910442 + ], + "user": 14513320000000, + "system": 14484590000000 + }, + "load": 0 + }, + "memory": { + "limit": 9223372036854776000, + "usage": 838025216, + "container_data": { + "pgfault": 71601418, + "pgmajfault": 664 + }, + "hierarchical_data": { + "pgfault": 100140566, + "pgmajfault": 1454 + } + } + }, + { + "timestamp": "2014-06-02T19:04:39.122826983Z", + "cpu": { + "usage": { + "total": 39238719219625, + "per_cpu": [ + 6601852483847, + 5399630546695, + 4705091917507, + 4516318924052, + 4585791208645, + 4556612305230, + 4479432924370, + 4393988909279 + ], + "user": 14513340000000, + "system": 14484630000000 + }, + "load": 0 + }, + "memory": { + "limit": 9223372036854776000, + "usage": 838131712, + "container_data": { + "pgfault": 71601418, + "pgmajfault": 664 + }, + "hierarchical_data": { + "pgfault": 100145700, + "pgmajfault": 1454 + } + } + } + ] +} + ` + containerName := "/some/container" + respStr = fmt.Sprintf(respStr, containerName, containerName, containerName) + client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.0/containers%v", containerName), respStr) + if err != nil { + t.Fatalf("unable to get a client %v", err) + } + defer server.Close() + err = testGetJsonData(respStr, &info.ContainerInfo{}, func() (interface{}, error) { + return client.ContainerInfo(containerName) + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/container/container.go b/container/container.go new file mode 100644 index 00000000..b757ce86 --- /dev/null +++ b/container/container.go @@ -0,0 +1,46 @@ +// Copyright 2014 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 container + +import ( + "fmt" + + "github.com/google/cadvisor/info" +) + +// Listing types. +const ( + LIST_SELF = iota + LIST_RECURSIVE +) + +type ListType int + +// Interface for container operation handlers. +type ContainerHandler interface { + GetSpec() (*info.ContainerSpec, error) + GetStats() (*info.ContainerStats, error) + ListContainers(listType ListType) ([]string, error) + ListThreads(listType ListType) ([]int, error) + ListProcesses(listType ListType) ([]int, error) + StatsSummary() (*info.ContainerStatsSummary, error) +} + +type NoStatsSummary struct { +} + +func (self *NoStatsSummary) StatsSummary() (*info.ContainerStatsSummary, error) { + return nil, fmt.Errorf("This method (StatsSummary) should never be called") +} diff --git a/container/docker/factory.go b/container/docker/factory.go new file mode 100644 index 00000000..6630c712 --- /dev/null +++ b/container/docker/factory.go @@ -0,0 +1,104 @@ +// Copyright 2014 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 docker + +import ( + "flag" + "fmt" + "regexp" + "strconv" + + "github.com/fsouza/go-dockerclient" + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/info" +) + +var argDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "docker endpoint") + +type dockerFactory struct { + machineInfoFactory info.MachineInfoFactory +} + +func (self *dockerFactory) String() string { + return "docker" +} + +func (self *dockerFactory) NewContainerHandler(name string) (handler container.ContainerHandler, err error) { + client, err := docker.NewClient(*argDockerEndpoint) + if err != nil { + return + } + handler = &dockerContainerHandler{ + client: client, + name: name, + machineInfoFactory: self.machineInfoFactory, + } + return +} + +func parseDockerVersion(full_version_string string) ([]int, error) { + version_regexp_string := "(\\d+)\\.(\\d+)\\.(\\d+)" + version_re := regexp.MustCompile(version_regexp_string) + matches := version_re.FindAllStringSubmatch(full_version_string, -1) + if len(matches) != 1 { + return nil, fmt.Errorf("Version string \"%v\" doesn't match expected regular expression: \"%v\"", full_version_string, version_regexp_string) + } + version_string_array := matches[0][1:] + version_array := make([]int, 3) + for index, version_string := range version_string_array { + version, err := strconv.Atoi(version_string) + if err != nil { + return nil, fmt.Errorf("Error while parsing \"%v\" in \"%v\"", version_string, full_version_string) + } + version_array[index] = version + } + return version_array, nil +} + +// Register root container before running this function! +func Register(factory info.MachineInfoFactory, paths ...string) error { + client, err := docker.NewClient(*argDockerEndpoint) + if err != nil { + return fmt.Errorf("unable to communicate with docker daemon: %v", err) + } + if version, err := client.Version(); err != nil { + return fmt.Errorf("unable to communicate with docker daemon: %v", err) + } else { + expected_version := []int{0, 11, 1} + version_string := version.Get("Version") + version, err := parseDockerVersion(version_string) + if err != nil { + return fmt.Errorf("Couldn't parse docker version: %v", err) + } + for index, number := range version { + if number > expected_version[index] { + break + } else if number < expected_version[index] { + return fmt.Errorf("cAdvisor requires docker version above %v but we have found version %v reported as \"%v\"", expected_version, version, version_string) + } + } + } + f := &dockerFactory{ + machineInfoFactory: factory, + } + cf := container.AddStatsSummaryToFactory(f) + for _, p := range paths { + if p != "/" && p != "/docker" { + return fmt.Errorf("%v cannot be managed by docker", p) + } + container.RegisterContainerHandlerFactory(p, cf) + } + return nil +} diff --git a/container/docker/handler.go b/container/docker/handler.go new file mode 100644 index 00000000..15a36542 --- /dev/null +++ b/container/docker/handler.go @@ -0,0 +1,319 @@ +// Copyright 2014 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 docker + +import ( + "bufio" + "encoding/json" + "fmt" + "math" + "os" + "path" + "strings" + "time" + + "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" + "github.com/fsouza/go-dockerclient" + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/info" +) + +type dockerContainerHandler struct { + client *docker.Client + name string + machineInfoFactory info.MachineInfoFactory + container.NoStatsSummary +} + +func (self *dockerContainerHandler) splitName() (string, string, error) { + parent, id := path.Split(self.name) + cgroupSelf, err := os.Open("/proc/self/cgroup") + if err != nil { + return "", "", err + } + scanner := bufio.NewScanner(cgroupSelf) + + subsys := []string{"memory", "cpu"} + nestedLevels := 0 + for scanner.Scan() { + line := scanner.Text() + elems := strings.Split(line, ":") + if len(elems) < 3 { + continue + } + for _, s := range subsys { + if elems[1] == s { + // count how many nested docker containers are there. + nestedLevels = strings.Count(elems[2], "/docker") + break + } + } + } + if nestedLevels > 0 { + // we are running inside a docker container + upperLevel := strings.Repeat("../../", nestedLevels) + //parent = strings.Join([]string{parent, upperLevel}, "/") + parent = fmt.Sprintf("%v%v", upperLevel, parent) + } + return parent, id, nil +} + +func (self *dockerContainerHandler) isDockerRoot() bool { + // TODO(dengnan): Should we consider other cases? + return self.name == "/docker" +} + +func (self *dockerContainerHandler) isRootContainer() bool { + return self.name == "/" +} + +func (self *dockerContainerHandler) isDockerContainer() bool { + return (!self.isDockerRoot()) && (!self.isRootContainer()) +} + +type dockerPortBinding struct { + HostIp string + HostPort string +} + +type dockerPort string +type dockerPortMap map[dockerPort][]dockerPortBinding + +type dockerNetworkSettings struct { + IPAddress string + IPPrefixLen int + Gateway string + Bridge string + Ports dockerPortMap +} + +type dockerContainerConfig struct { + Hostname string + Domainname string + User string + Memory int64 // Memory limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 // CPU shares (relative weight vs. other containers) + Cpuset string // Cpuset 0-2, 0,1 + AttachStdin bool + AttachStdout bool + AttachStderr bool + PortSpecs []string // Deprecated - Can be in the format of 8080/tcp + ExposedPorts map[nat.Port]struct{} + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin + StdinOnce bool // If true, close stdin after the 1 attached client disconnects. + Env []string + Cmd []string + Image string // Name of the image as it was passed by the operator (eg. could be symbolic) + Volumes map[string]struct{} + WorkingDir string + Entrypoint []string + NetworkDisabled bool + OnBuild []string +} + +type dockerState struct { + Running bool + Pid int + ExitCode int + StartedAt time.Time + FinishedAt time.Time +} + +type dockerContainerSpec struct { + ID string + + Created time.Time + + Path string + Args []string + + Config *dockerContainerConfig + State dockerState + Image string + + NetworkSettings *dockerNetworkSettings + + ResolvConfPath string + HostnamePath string + HostsPath string + Name string + Driver string + ExecDriver string + + MountLabel, ProcessLabel string + + Volumes map[string]string + // Store rw/ro in a separate structure to preserve reverse-compatibility on-disk. + // Easier than migrating older container configs :) + VolumesRW map[string]bool + // contains filtered or unexported fields +} + +func readDockerSpec(id string) (spec *dockerContainerSpec, err error) { + dir := "/var/lib/docker/containers" + configPath := path.Join(dir, id, "config.json") + f, err := os.Open(configPath) + if err != nil { + return + } + defer f.Close() + d := json.NewDecoder(f) + ret := new(dockerContainerSpec) + err = d.Decode(ret) + if err != nil { + return + } + spec = ret + return +} + +func dockerConfigToContainerSpec(config *dockerContainerSpec, mi *info.MachineInfo) *info.ContainerSpec { + spec := new(info.ContainerSpec) + spec.Memory = new(info.MemorySpec) + spec.Memory.Limit = math.MaxUint64 + spec.Memory.SwapLimit = math.MaxUint64 + if config.Config.Memory > 0 { + spec.Memory.Limit = uint64(config.Config.Memory) + } + if config.Config.MemorySwap > 0 { + spec.Memory.SwapLimit = uint64(config.Config.MemorySwap - config.Config.Memory) + } + if mi != nil { + spec.Cpu = new(info.CpuSpec) + spec.Cpu.Limit = math.MaxUint64 + n := mi.NumCores / 64 + if mi.NumCores%64 > 0 { + n++ + } + spec.Cpu.Mask.Data = make([]uint64, n) + for i := 0; i < n; i++ { + spec.Cpu.Mask.Data[i] = math.MaxUint64 + } + } + return spec +} + +func (self *dockerContainerHandler) GetSpec() (spec *info.ContainerSpec, err error) { + if !self.isDockerContainer() { + spec = new(info.ContainerSpec) + return + } + mi, err := self.machineInfoFactory.GetMachineInfo() + if err != nil { + return + } + _, id, err := self.splitName() + if err != nil { + return + } + dspec, err := readDockerSpec(id) + if err != nil { + return + } + + spec = dockerConfigToContainerSpec(dspec, mi) + return +} + +func libcontainerToContainerStats(s *cgroups.Stats, mi *info.MachineInfo) *info.ContainerStats { + ret := new(info.ContainerStats) + ret.Timestamp = time.Now() + ret.Cpu = new(info.CpuStats) + ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode + ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode + n := len(s.CpuStats.CpuUsage.PercpuUsage) + ret.Cpu.Usage.PerCpu = make([]uint64, n) + + ret.Cpu.Usage.Total = 0 + for i := 0; i < n; i++ { + ret.Cpu.Usage.PerCpu[i] = s.CpuStats.CpuUsage.PercpuUsage[i] + ret.Cpu.Usage.Total += s.CpuStats.CpuUsage.PercpuUsage[i] + } + ret.Memory = new(info.MemoryStats) + ret.Memory.Usage = s.MemoryStats.Usage + if v, ok := s.MemoryStats.Stats["pgfault"]; ok { + ret.Memory.ContainerData.Pgfault = v + ret.Memory.HierarchicalData.Pgfault = v + } + if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok { + ret.Memory.ContainerData.Pgmajfault = v + ret.Memory.HierarchicalData.Pgmajfault = v + } + return ret +} + +func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err error) { + if !self.isDockerContainer() { + return + } + mi, err := self.machineInfoFactory.GetMachineInfo() + if err != nil { + return + } + parent, id, err := self.splitName() + if err != nil { + return + } + cg := &cgroups.Cgroup{ + Parent: parent, + Name: id, + } + s, err := fs.GetStats(cg) + if err != nil { + return + } + stats = libcontainerToContainerStats(s, mi) + return +} + +func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]string, error) { + if self.isDockerContainer() { + return nil, nil + } + if self.isRootContainer() && listType == container.LIST_SELF { + return []string{"/docker"}, nil + } + opt := docker.ListContainersOptions{ + All: true, + } + containers, err := self.client.ListContainers(opt) + if err != nil { + return nil, err + } + ret := make([]string, 0, len(containers)+1) + for _, c := range containers { + if !strings.HasPrefix(c.Status, "Up ") { + continue + } + path := fmt.Sprintf("/docker/%v", c.ID) + ret = append(ret, path) + } + if self.isRootContainer() { + ret = append(ret, "/docker") + } + return ret, nil +} + +func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]int, error) { + return nil, nil +} + +func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { + return nil, nil +} diff --git a/container/factory.go b/container/factory.go new file mode 100644 index 00000000..97d43a45 --- /dev/null +++ b/container/factory.go @@ -0,0 +1,129 @@ +// Copyright 2014 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 container + +import ( + "fmt" + "log" + "strings" + "sync" +) + +type ContainerHandlerFactory interface { + NewContainerHandler(name string) (ContainerHandler, error) + + // for testability + String() string +} + +type factoryTreeNode struct { + defaultFactory ContainerHandlerFactory + children map[string]*factoryTreeNode +} + +func (self *factoryTreeNode) find(elems ...string) ContainerHandlerFactory { + node := self + for _, elem := range elems { + if len(node.children) == 0 { + break + } + if child, ok := node.children[elem]; ok { + node = child + } else { + return node.defaultFactory + } + } + + return node.defaultFactory +} + +func (self *factoryTreeNode) add(factory ContainerHandlerFactory, elems ...string) { + node := self + for _, elem := range elems { + if node.children == nil { + node.children = make(map[string]*factoryTreeNode, 16) + } + child, ok := self.children[elem] + if !ok { + child = &factoryTreeNode{ + defaultFactory: node.defaultFactory, + children: make(map[string]*factoryTreeNode, 16), + } + node.children[elem] = child + } + node = child + } + node.defaultFactory = factory +} + +type factoryManager struct { + root *factoryTreeNode + lock sync.RWMutex +} + +func dropEmptyString(elems ...string) []string { + ret := make([]string, 0, len(elems)) + for _, e := range elems { + if len(e) > 0 { + ret = append(ret, e) + } + } + return ret +} + +// Must register factory for root container! +func (self *factoryManager) Register(path string, factory ContainerHandlerFactory) { + self.lock.Lock() + defer self.lock.Unlock() + + if self.root == nil { + self.root = &factoryTreeNode{ + defaultFactory: nil, + children: make(map[string]*factoryTreeNode, 10), + } + } + + elems := dropEmptyString(strings.Split(path, "/")...) + self.root.add(factory, elems...) +} + +func (self *factoryManager) NewContainerHandler(path string) (ContainerHandler, error) { + self.lock.RLock() + defer self.lock.RUnlock() + + if self.root == nil { + err := fmt.Errorf("nil factory for container %v: no factory registered", path) + return nil, err + } + + elems := dropEmptyString(strings.Split(path, "/")...) + factory := self.root.find(elems...) + if factory == nil { + err := fmt.Errorf("nil factory for container %v", path) + return nil, err + } + log.Printf("container handler factory for %v is %v\n", path, factory) + return factory.NewContainerHandler(path) +} + +var globalFactoryManager factoryManager + +func RegisterContainerHandlerFactory(path string, factory ContainerHandlerFactory) { + globalFactoryManager.Register(path, factory) +} + +func NewContainerHandler(path string) (ContainerHandler, error) { + return globalFactoryManager.NewContainerHandler(path) +} diff --git a/container/factory_test.go b/container/factory_test.go new file mode 100644 index 00000000..1a688417 --- /dev/null +++ b/container/factory_test.go @@ -0,0 +1,76 @@ +// Copyright 2014 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 container + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/mock" +) + +type mockContainerHandlerFactory struct { + mock.Mock + Name string +} + +func (self *mockContainerHandlerFactory) String() string { + return self.Name +} + +func (self *mockContainerHandlerFactory) NewContainerHandler(name string) (ContainerHandler, error) { + args := self.Called(name) + return args.Get(0).(ContainerHandler), args.Error(1) +} + +func testExpectedFactory(root *factoryTreeNode, path, expectedFactory string, t *testing.T) { + elems := dropEmptyString(strings.Split(path, "/")...) + factory := root.find(elems...) + if factory.String() != expectedFactory { + t.Errorf("factory %v should be used to create container %v. but %v is selected", + expectedFactory, + path, + factory) + } +} + +func testAddFactory(root *factoryTreeNode, path string) *factoryTreeNode { + elems := dropEmptyString(strings.Split(path, "/")...) + if root == nil { + root = &factoryTreeNode{ + defaultFactory: nil, + } + } + f := &mockContainerHandlerFactory{ + Name: path, + } + root.add(f, elems...) + return root +} + +func TestFactoryTree(t *testing.T) { + root := testAddFactory(nil, "/") + root = testAddFactory(root, "/docker") + root = testAddFactory(root, "/user") + root = testAddFactory(root, "/user/special/containers") + + testExpectedFactory(root, "/docker/container", "/docker", t) + testExpectedFactory(root, "/docker", "/docker", t) + testExpectedFactory(root, "/", "/", t) + testExpectedFactory(root, "/user/deep/level/container", "/user", t) + testExpectedFactory(root, "/user/special/containers", "/user/special/containers", t) + testExpectedFactory(root, "/user/special/containers/container", "/user/special/containers", t) + testExpectedFactory(root, "/other", "/", t) +} diff --git a/container/filter.go b/container/filter.go new file mode 100644 index 00000000..0495bdc7 --- /dev/null +++ b/container/filter.go @@ -0,0 +1,90 @@ +// Copyright 2014 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 container + +import ( + "strings" + + "github.com/google/cadvisor/info" +) + +type containerListFilter struct { + filter func(string) bool + handler ContainerHandler + NoStatsSummary +} + +func (self *containerListFilter) GetSpec() (*info.ContainerSpec, error) { + return self.handler.GetSpec() +} + +func (self *containerListFilter) GetStats() (*info.ContainerStats, error) { + return self.handler.GetStats() +} + +func (self *containerListFilter) ListContainers(listType ListType) ([]string, error) { + containers, err := self.handler.ListContainers(listType) + if err != nil { + return nil, err + } + if len(containers) == 0 { + return nil, nil + } + ret := make([]string, 0, len(containers)) + for _, c := range containers { + if self.filter(c) { + ret = append(ret, c) + } + } + return ret, nil +} + +func (self *containerListFilter) ListThreads(listType ListType) ([]int, error) { + return self.handler.ListThreads(listType) +} + +func (self *containerListFilter) ListProcesses(listType ListType) ([]int, error) { + return self.handler.ListProcesses(listType) +} + +func NewWhiteListFilter(handler ContainerHandler, acceptedPaths ...string) ContainerHandler { + filter := func(p string) bool { + for _, path := range acceptedPaths { + if strings.HasPrefix(p, path) { + return true + } + } + return false + } + return &containerListFilter{ + filter: filter, + handler: handler, + } +} + +func NewBlackListFilter(handler ContainerHandler, forbiddenPaths ...string) ContainerHandler { + filter := func(p string) bool { + for _, path := range forbiddenPaths { + if strings.HasPrefix(p, path) { + return false + } + } + return true + } + return &containerListFilter{ + filter: filter, + handler: handler, + } +} diff --git a/container/filter_test.go b/container/filter_test.go new file mode 100644 index 00000000..b6c2d41a --- /dev/null +++ b/container/filter_test.go @@ -0,0 +1,123 @@ +// Copyright 2014 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 container + +import ( + "strings" + "testing" + + "github.com/google/cadvisor/info" + "github.com/stretchr/testify/mock" +) + +type mockContainerHandler struct { + mock.Mock + NoStatsSummary +} + +func (self *mockContainerHandler) GetSpec() (*info.ContainerSpec, error) { + args := self.Called() + return args.Get(0).(*info.ContainerSpec), args.Error(1) +} + +func (self *mockContainerHandler) GetStats() (*info.ContainerStats, error) { + args := self.Called() + return args.Get(0).(*info.ContainerStats), args.Error(1) +} + +func (self *mockContainerHandler) ListContainers(listType ListType) ([]string, error) { + args := self.Called(listType) + return args.Get(0).([]string), args.Error(1) +} + +func (self *mockContainerHandler) ListThreads(listType ListType) ([]int, error) { + args := self.Called(listType) + return args.Get(0).([]int), args.Error(1) +} + +func (self *mockContainerHandler) ListProcesses(listType ListType) ([]int, error) { + args := self.Called(listType) + return args.Get(0).([]int), args.Error(1) +} + +func TestWhiteListContainerFilter(t *testing.T) { + mockc := &mockContainerHandler{} + mockc.On("ListContainers", LIST_RECURSIVE).Return( + []string{ + "/docker/ee0103", + "/container/created/by/lmctfy", + "/user/something", + }, + nil, + ) + + filterPaths := []string{ + "/docker", + "/container", + } + + fc := NewWhiteListFilter(mockc, filterPaths...) + containers, err := fc.ListContainers(LIST_RECURSIVE) + if err != nil { + t.Fatal(err) + } + for _, c := range containers { + legal := false + for _, prefix := range filterPaths { + if strings.HasPrefix(c, prefix) { + legal = true + } + } + if !legal { + t.Errorf("%v is not in the white list", c) + } + } + mockc.AssertExpectations(t) +} + +func TestBlackListContainerFilter(t *testing.T) { + mockc := &mockContainerHandler{} + mockc.On("ListContainers", LIST_RECURSIVE).Return( + []string{ + "/docker/ee0103", + "/container/created/by/lmctfy", + "/user/something", + }, + nil, + ) + + filterPaths := []string{ + "/docker", + "/container", + } + + fc := NewBlackListFilter(mockc, filterPaths...) + containers, err := fc.ListContainers(LIST_RECURSIVE) + if err != nil { + t.Fatal(err) + } + for _, c := range containers { + legal := true + for _, prefix := range filterPaths { + if strings.HasPrefix(c, prefix) { + legal = false + } + } + if !legal { + t.Errorf("%v is in the black list", c) + } + } + mockc.AssertExpectations(t) +} diff --git a/container/lmctfy/factory.go b/container/lmctfy/factory.go new file mode 100644 index 00000000..88040313 --- /dev/null +++ b/container/lmctfy/factory.go @@ -0,0 +1,52 @@ +// Copyright 2014 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 lmctfy + +import ( + "errors" + "log" + "os/exec" + + "github.com/google/cadvisor/container" +) + +func Register(paths ...string) error { + if _, err := exec.LookPath("lmctfy"); err != nil { + return errors.New("cannot find lmctfy") + } + f := container.AddStatsSummaryToFactory(&lmctfyFactory{}) + for _, path := range paths { + log.Printf("register lmctfy under %v", path) + container.RegisterContainerHandlerFactory(path, f) + } + return nil +} + +type lmctfyFactory struct { +} + +func (self *lmctfyFactory) String() string { + return "lmctfy" +} + +func (self *lmctfyFactory) NewContainerHandler(name string) (container.ContainerHandler, error) { + c, err := New(name) + if err != nil { + return nil, err + } + // XXX(dengnan): /user is created by ubuntu 14.04. Not sure if we should list it + handler := container.NewBlackListFilter(c, "/user") + return handler, nil +} diff --git a/container/lmctfy/lmctfy.pb.go b/container/lmctfy/lmctfy.pb.go new file mode 100644 index 00000000..d350d216 --- /dev/null +++ b/container/lmctfy/lmctfy.pb.go @@ -0,0 +1,2103 @@ +// Copyright 2014 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. + +// Code generated by protoc-gen-go. +// source: lmctfy.proto +// DO NOT EDIT! + +/* +Package containers is a generated protocol buffer package. + +It is generated from these files: + lmctfy.proto + virtual_host.proto + +It has these top-level messages: + ContainerSpec + EventSpec + RunSpec + VirtualHostSpec + CpuSpec + MemorySpec + BlockIoSpec + NetworkSpec + MonitoringSpec + FilesystemSpec + DeviceSpec + SecuritySpec + ContainerStats + HistogramMap + ThrottlingData + CpuStats + MemoryStats + BlockIoStats + NetworkStats + MonitoringStats + FilesystemStats +*/ +package lmctfy + +import proto "code.google.com/p/goprotobuf/proto" +import json "encoding/json" +import math "math" + +// Reference proto, json, and math imports to suppress error if they are not otherwise used. +var _ = proto.Marshal +var _ = &json.SyntaxError{} +var _ = math.Inf + +type SchedulingLatency int32 + +const ( + SchedulingLatency_BEST_EFFORT SchedulingLatency = 1 + SchedulingLatency_NORMAL SchedulingLatency = 2 + SchedulingLatency_PRIORITY SchedulingLatency = 3 + SchedulingLatency_PREMIER SchedulingLatency = 4 +) + +var SchedulingLatency_name = map[int32]string{ + 1: "BEST_EFFORT", + 2: "NORMAL", + 3: "PRIORITY", + 4: "PREMIER", +} +var SchedulingLatency_value = map[string]int32{ + "BEST_EFFORT": 1, + "NORMAL": 2, + "PRIORITY": 3, + "PREMIER": 4, +} + +func (x SchedulingLatency) Enum() *SchedulingLatency { + p := new(SchedulingLatency) + *p = x + return p +} +func (x SchedulingLatency) String() string { + return proto.EnumName(SchedulingLatency_name, int32(x)) +} +func (x *SchedulingLatency) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(SchedulingLatency_value, data, "SchedulingLatency") + if err != nil { + return err + } + *x = SchedulingLatency(value) + return nil +} + +type CpuHistogramType int32 + +const ( + CpuHistogramType_SERVE CpuHistogramType = 1 + CpuHistogramType_ONCPU CpuHistogramType = 2 + CpuHistogramType_SLEEP CpuHistogramType = 3 + CpuHistogramType_QUEUE_SELF CpuHistogramType = 4 + CpuHistogramType_QUEUE_OTHER CpuHistogramType = 5 +) + +var CpuHistogramType_name = map[int32]string{ + 1: "SERVE", + 2: "ONCPU", + 3: "SLEEP", + 4: "QUEUE_SELF", + 5: "QUEUE_OTHER", +} +var CpuHistogramType_value = map[string]int32{ + "SERVE": 1, + "ONCPU": 2, + "SLEEP": 3, + "QUEUE_SELF": 4, + "QUEUE_OTHER": 5, +} + +func (x CpuHistogramType) Enum() *CpuHistogramType { + p := new(CpuHistogramType) + *p = x + return p +} +func (x CpuHistogramType) String() string { + return proto.EnumName(CpuHistogramType_name, int32(x)) +} +func (x *CpuHistogramType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(CpuHistogramType_value, data, "CpuHistogramType") + if err != nil { + return err + } + *x = CpuHistogramType(value) + return nil +} + +type RunSpec_FdPolicy int32 + +const ( + RunSpec_UNKNOWN RunSpec_FdPolicy = 0 + RunSpec_INHERIT RunSpec_FdPolicy = 1 + RunSpec_DETACHED RunSpec_FdPolicy = 2 +) + +var RunSpec_FdPolicy_name = map[int32]string{ + 0: "UNKNOWN", + 1: "INHERIT", + 2: "DETACHED", +} +var RunSpec_FdPolicy_value = map[string]int32{ + "UNKNOWN": 0, + "INHERIT": 1, + "DETACHED": 2, +} + +func (x RunSpec_FdPolicy) Enum() *RunSpec_FdPolicy { + p := new(RunSpec_FdPolicy) + *p = x + return p +} +func (x RunSpec_FdPolicy) String() string { + return proto.EnumName(RunSpec_FdPolicy_name, int32(x)) +} +func (x *RunSpec_FdPolicy) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(RunSpec_FdPolicy_value, data, "RunSpec_FdPolicy") + if err != nil { + return err + } + *x = RunSpec_FdPolicy(value) + return nil +} + +type BlockIoSpec_OpType int32 + +const ( + BlockIoSpec_READ BlockIoSpec_OpType = 1 + BlockIoSpec_WRITE BlockIoSpec_OpType = 2 +) + +var BlockIoSpec_OpType_name = map[int32]string{ + 1: "READ", + 2: "WRITE", +} +var BlockIoSpec_OpType_value = map[string]int32{ + "READ": 1, + "WRITE": 2, +} + +func (x BlockIoSpec_OpType) Enum() *BlockIoSpec_OpType { + p := new(BlockIoSpec_OpType) + *p = x + return p +} +func (x BlockIoSpec_OpType) String() string { + return proto.EnumName(BlockIoSpec_OpType_name, int32(x)) +} +func (x *BlockIoSpec_OpType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(BlockIoSpec_OpType_value, data, "BlockIoSpec_OpType") + if err != nil { + return err + } + *x = BlockIoSpec_OpType(value) + return nil +} + +type BlockIoSpec_LimitType int32 + +const ( + BlockIoSpec_BYTES_PER_SECOND BlockIoSpec_LimitType = 1 + BlockIoSpec_IO_PER_SECOND BlockIoSpec_LimitType = 2 +) + +var BlockIoSpec_LimitType_name = map[int32]string{ + 1: "BYTES_PER_SECOND", + 2: "IO_PER_SECOND", +} +var BlockIoSpec_LimitType_value = map[string]int32{ + "BYTES_PER_SECOND": 1, + "IO_PER_SECOND": 2, +} + +func (x BlockIoSpec_LimitType) Enum() *BlockIoSpec_LimitType { + p := new(BlockIoSpec_LimitType) + *p = x + return p +} +func (x BlockIoSpec_LimitType) String() string { + return proto.EnumName(BlockIoSpec_LimitType_name, int32(x)) +} +func (x *BlockIoSpec_LimitType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(BlockIoSpec_LimitType_value, data, "BlockIoSpec_LimitType") + if err != nil { + return err + } + *x = BlockIoSpec_LimitType(value) + return nil +} + +type DeviceSpec_DeviceType int32 + +const ( + DeviceSpec_DEVICE_CHAR DeviceSpec_DeviceType = 0 + DeviceSpec_DEVICE_BLOCK DeviceSpec_DeviceType = 1 + DeviceSpec_DEVICE_ALL DeviceSpec_DeviceType = 2 +) + +var DeviceSpec_DeviceType_name = map[int32]string{ + 0: "DEVICE_CHAR", + 1: "DEVICE_BLOCK", + 2: "DEVICE_ALL", +} +var DeviceSpec_DeviceType_value = map[string]int32{ + "DEVICE_CHAR": 0, + "DEVICE_BLOCK": 1, + "DEVICE_ALL": 2, +} + +func (x DeviceSpec_DeviceType) Enum() *DeviceSpec_DeviceType { + p := new(DeviceSpec_DeviceType) + *p = x + return p +} +func (x DeviceSpec_DeviceType) String() string { + return proto.EnumName(DeviceSpec_DeviceType_name, int32(x)) +} +func (x *DeviceSpec_DeviceType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(DeviceSpec_DeviceType_value, data, "DeviceSpec_DeviceType") + if err != nil { + return err + } + *x = DeviceSpec_DeviceType(value) + return nil +} + +type DeviceSpec_DeviceAccess int32 + +const ( + DeviceSpec_READ DeviceSpec_DeviceAccess = 1 + DeviceSpec_WRITE DeviceSpec_DeviceAccess = 2 + DeviceSpec_MKNOD DeviceSpec_DeviceAccess = 3 +) + +var DeviceSpec_DeviceAccess_name = map[int32]string{ + 1: "READ", + 2: "WRITE", + 3: "MKNOD", +} +var DeviceSpec_DeviceAccess_value = map[string]int32{ + "READ": 1, + "WRITE": 2, + "MKNOD": 3, +} + +func (x DeviceSpec_DeviceAccess) Enum() *DeviceSpec_DeviceAccess { + p := new(DeviceSpec_DeviceAccess) + *p = x + return p +} +func (x DeviceSpec_DeviceAccess) String() string { + return proto.EnumName(DeviceSpec_DeviceAccess_name, int32(x)) +} +func (x *DeviceSpec_DeviceAccess) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(DeviceSpec_DeviceAccess_value, data, "DeviceSpec_DeviceAccess") + if err != nil { + return err + } + *x = DeviceSpec_DeviceAccess(value) + return nil +} + +type DeviceSpec_DevicePermission int32 + +const ( + DeviceSpec_ALLOW DeviceSpec_DevicePermission = 1 + DeviceSpec_DENY DeviceSpec_DevicePermission = 2 +) + +var DeviceSpec_DevicePermission_name = map[int32]string{ + 1: "ALLOW", + 2: "DENY", +} +var DeviceSpec_DevicePermission_value = map[string]int32{ + "ALLOW": 1, + "DENY": 2, +} + +func (x DeviceSpec_DevicePermission) Enum() *DeviceSpec_DevicePermission { + p := new(DeviceSpec_DevicePermission) + *p = x + return p +} +func (x DeviceSpec_DevicePermission) String() string { + return proto.EnumName(DeviceSpec_DevicePermission_name, int32(x)) +} +func (x *DeviceSpec_DevicePermission) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(DeviceSpec_DevicePermission_value, data, "DeviceSpec_DevicePermission") + if err != nil { + return err + } + *x = DeviceSpec_DevicePermission(value) + return nil +} + +type ContainerSpec struct { + Owner *int64 `protobuf:"varint,1,opt,name=owner" json:"owner,omitempty"` + OwnerGroup *int64 `protobuf:"varint,8,opt,name=owner_group" json:"owner_group,omitempty"` + ChildrenLimit *int64 `protobuf:"varint,9,opt,name=children_limit" json:"children_limit,omitempty"` + Cpu *CpuSpec `protobuf:"bytes,2,opt,name=cpu" json:"cpu,omitempty"` + Memory *MemorySpec `protobuf:"bytes,3,opt,name=memory" json:"memory,omitempty"` + Network *NetworkSpec `protobuf:"bytes,5,opt,name=network" json:"network,omitempty"` + Blockio *BlockIoSpec `protobuf:"bytes,12,opt,name=blockio" json:"blockio,omitempty"` + Monitoring *MonitoringSpec `protobuf:"bytes,6,opt,name=monitoring" json:"monitoring,omitempty"` + Filesystem *FilesystemSpec `protobuf:"bytes,7,opt,name=filesystem" json:"filesystem,omitempty"` + Device *DeviceSpec `protobuf:"bytes,11,opt,name=device" json:"device,omitempty"` + VirtualHost *VirtualHostSpec `protobuf:"bytes,10,opt,name=virtual_host" json:"virtual_host,omitempty"` + SecuritySpec *SecuritySpec `protobuf:"bytes,13,opt,name=security_spec" json:"security_spec,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ContainerSpec) Reset() { *m = ContainerSpec{} } +func (m *ContainerSpec) String() string { return proto.CompactTextString(m) } +func (*ContainerSpec) ProtoMessage() {} + +func (m *ContainerSpec) GetOwner() int64 { + if m != nil && m.Owner != nil { + return *m.Owner + } + return 0 +} + +func (m *ContainerSpec) GetOwnerGroup() int64 { + if m != nil && m.OwnerGroup != nil { + return *m.OwnerGroup + } + return 0 +} + +func (m *ContainerSpec) GetChildrenLimit() int64 { + if m != nil && m.ChildrenLimit != nil { + return *m.ChildrenLimit + } + return 0 +} + +func (m *ContainerSpec) GetCpu() *CpuSpec { + if m != nil { + return m.Cpu + } + return nil +} + +func (m *ContainerSpec) GetMemory() *MemorySpec { + if m != nil { + return m.Memory + } + return nil +} + +func (m *ContainerSpec) GetNetwork() *NetworkSpec { + if m != nil { + return m.Network + } + return nil +} + +func (m *ContainerSpec) GetBlockio() *BlockIoSpec { + if m != nil { + return m.Blockio + } + return nil +} + +func (m *ContainerSpec) GetMonitoring() *MonitoringSpec { + if m != nil { + return m.Monitoring + } + return nil +} + +func (m *ContainerSpec) GetFilesystem() *FilesystemSpec { + if m != nil { + return m.Filesystem + } + return nil +} + +func (m *ContainerSpec) GetDevice() *DeviceSpec { + if m != nil { + return m.Device + } + return nil +} + +func (m *ContainerSpec) GetVirtualHost() *VirtualHostSpec { + if m != nil { + return m.VirtualHost + } + return nil +} + +func (m *ContainerSpec) GetSecuritySpec() *SecuritySpec { + if m != nil { + return m.SecuritySpec + } + return nil +} + +type EventSpec struct { + Oom *EventSpec_Oom `protobuf:"bytes,1,opt,name=oom" json:"oom,omitempty"` + MemoryThreshold *EventSpec_MemoryThreshold `protobuf:"bytes,2,opt,name=memory_threshold" json:"memory_threshold,omitempty"` + ContainerEmpty *EventSpec_ContainerEmpty `protobuf:"bytes,3,opt,name=container_empty" json:"container_empty,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EventSpec) Reset() { *m = EventSpec{} } +func (m *EventSpec) String() string { return proto.CompactTextString(m) } +func (*EventSpec) ProtoMessage() {} + +func (m *EventSpec) GetOom() *EventSpec_Oom { + if m != nil { + return m.Oom + } + return nil +} + +func (m *EventSpec) GetMemoryThreshold() *EventSpec_MemoryThreshold { + if m != nil { + return m.MemoryThreshold + } + return nil +} + +func (m *EventSpec) GetContainerEmpty() *EventSpec_ContainerEmpty { + if m != nil { + return m.ContainerEmpty + } + return nil +} + +type EventSpec_Oom struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *EventSpec_Oom) Reset() { *m = EventSpec_Oom{} } +func (m *EventSpec_Oom) String() string { return proto.CompactTextString(m) } +func (*EventSpec_Oom) ProtoMessage() {} + +type EventSpec_MemoryThreshold struct { + Usage *int64 `protobuf:"varint,1,opt,name=usage" json:"usage,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EventSpec_MemoryThreshold) Reset() { *m = EventSpec_MemoryThreshold{} } +func (m *EventSpec_MemoryThreshold) String() string { return proto.CompactTextString(m) } +func (*EventSpec_MemoryThreshold) ProtoMessage() {} + +func (m *EventSpec_MemoryThreshold) GetUsage() int64 { + if m != nil && m.Usage != nil { + return *m.Usage + } + return 0 +} + +type EventSpec_ContainerEmpty struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *EventSpec_ContainerEmpty) Reset() { *m = EventSpec_ContainerEmpty{} } +func (m *EventSpec_ContainerEmpty) String() string { return proto.CompactTextString(m) } +func (*EventSpec_ContainerEmpty) ProtoMessage() {} + +type RunSpec struct { + FdPolicy *RunSpec_FdPolicy `protobuf:"varint,1,opt,name=fd_policy,enum=containers.RunSpec_FdPolicy" json:"fd_policy,omitempty"` + Console *RunSpec_Console `protobuf:"bytes,2,opt,name=console" json:"console,omitempty"` + ApparmorProfile *string `protobuf:"bytes,3,opt,name=apparmor_profile" json:"apparmor_profile,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *RunSpec) Reset() { *m = RunSpec{} } +func (m *RunSpec) String() string { return proto.CompactTextString(m) } +func (*RunSpec) ProtoMessage() {} + +func (m *RunSpec) GetFdPolicy() RunSpec_FdPolicy { + if m != nil && m.FdPolicy != nil { + return *m.FdPolicy + } + return RunSpec_UNKNOWN +} + +func (m *RunSpec) GetConsole() *RunSpec_Console { + if m != nil { + return m.Console + } + return nil +} + +func (m *RunSpec) GetApparmorProfile() string { + if m != nil && m.ApparmorProfile != nil { + return *m.ApparmorProfile + } + return "" +} + +type RunSpec_Console struct { + SlavePty *string `protobuf:"bytes,1,opt,name=slave_pty" json:"slave_pty,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *RunSpec_Console) Reset() { *m = RunSpec_Console{} } +func (m *RunSpec_Console) String() string { return proto.CompactTextString(m) } +func (*RunSpec_Console) ProtoMessage() {} + +func (m *RunSpec_Console) GetSlavePty() string { + if m != nil && m.SlavePty != nil { + return *m.SlavePty + } + return "" +} + +type VirtualHostSpec struct { + VirtualHostname *string `protobuf:"bytes,1,opt,name=virtual_hostname" json:"virtual_hostname,omitempty"` + Init *VirtualHostSpec_Init `protobuf:"bytes,2,opt,name=init" json:"init,omitempty"` + Network *Network `protobuf:"bytes,3,opt,name=network" json:"network,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *VirtualHostSpec) Reset() { *m = VirtualHostSpec{} } +func (m *VirtualHostSpec) String() string { return proto.CompactTextString(m) } +func (*VirtualHostSpec) ProtoMessage() {} + +func (m *VirtualHostSpec) GetVirtualHostname() string { + if m != nil && m.VirtualHostname != nil { + return *m.VirtualHostname + } + return "" +} + +func (m *VirtualHostSpec) GetInit() *VirtualHostSpec_Init { + if m != nil { + return m.Init + } + return nil +} + +func (m *VirtualHostSpec) GetNetwork() *Network { + if m != nil { + return m.Network + } + return nil +} + +type VirtualHostSpec_Init struct { + InitArgv []string `protobuf:"bytes,1,rep,name=init_argv" json:"init_argv,omitempty"` + RunSpec *RunSpec `protobuf:"bytes,2,opt,name=run_spec" json:"run_spec,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *VirtualHostSpec_Init) Reset() { *m = VirtualHostSpec_Init{} } +func (m *VirtualHostSpec_Init) String() string { return proto.CompactTextString(m) } +func (*VirtualHostSpec_Init) ProtoMessage() {} + +func (m *VirtualHostSpec_Init) GetInitArgv() []string { + if m != nil { + return m.InitArgv + } + return nil +} + +func (m *VirtualHostSpec_Init) GetRunSpec() *RunSpec { + if m != nil { + return m.RunSpec + } + return nil +} + +type CpuSpec struct { + SchedulingLatency *SchedulingLatency `protobuf:"varint,1,opt,name=scheduling_latency,enum=containers.SchedulingLatency" json:"scheduling_latency,omitempty"` + Limit *uint64 `protobuf:"varint,2,opt,name=limit" json:"limit,omitempty"` + MaxLimit *uint64 `protobuf:"varint,3,opt,name=max_limit" json:"max_limit,omitempty"` + Mask *CpuSpec_Mask `protobuf:"bytes,4,opt,name=mask" json:"mask,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CpuSpec) Reset() { *m = CpuSpec{} } +func (m *CpuSpec) String() string { return proto.CompactTextString(m) } +func (*CpuSpec) ProtoMessage() {} + +func (m *CpuSpec) GetSchedulingLatency() SchedulingLatency { + if m != nil && m.SchedulingLatency != nil { + return *m.SchedulingLatency + } + return SchedulingLatency_BEST_EFFORT +} + +func (m *CpuSpec) GetLimit() uint64 { + if m != nil && m.Limit != nil { + return *m.Limit + } + return 0 +} + +func (m *CpuSpec) GetMaxLimit() uint64 { + if m != nil && m.MaxLimit != nil { + return *m.MaxLimit + } + return 0 +} + +func (m *CpuSpec) GetMask() *CpuSpec_Mask { + if m != nil { + return m.Mask + } + return nil +} + +type CpuSpec_Mask struct { + Data []uint64 `protobuf:"varint,1,rep,name=data" json:"data,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CpuSpec_Mask) Reset() { *m = CpuSpec_Mask{} } +func (m *CpuSpec_Mask) String() string { return proto.CompactTextString(m) } +func (*CpuSpec_Mask) ProtoMessage() {} + +func (m *CpuSpec_Mask) GetData() []uint64 { + if m != nil { + return m.Data + } + return nil +} + +type MemorySpec struct { + EvictionPriority *int32 `protobuf:"varint,1,opt,name=eviction_priority" json:"eviction_priority,omitempty"` + Limit *int64 `protobuf:"varint,2,opt,name=limit" json:"limit,omitempty"` + MaxLimit *int64 `protobuf:"varint,3,opt,name=max_limit" json:"max_limit,omitempty"` + Reservation *int64 `protobuf:"varint,4,opt,name=reservation" json:"reservation,omitempty"` + HugetlbfsPath *string `protobuf:"bytes,5,opt,name=hugetlbfs_path" json:"hugetlbfs_path,omitempty"` + Tmpfs *MemorySpec_TmpfsSpec `protobuf:"bytes,6,opt,name=tmpfs" json:"tmpfs,omitempty"` + SwapLimit *int64 `protobuf:"varint,7,opt,name=swap_limit" json:"swap_limit,omitempty"` + CompressionSamplingRatio *int32 `protobuf:"varint,8,opt,name=compression_sampling_ratio" json:"compression_sampling_ratio,omitempty"` + StalePageAge *int32 `protobuf:"varint,9,opt,name=stale_page_age" json:"stale_page_age,omitempty"` + Dirty *MemorySpec_Dirty `protobuf:"bytes,10,opt,name=dirty" json:"dirty,omitempty"` + KmemChargeUsage *bool `protobuf:"varint,11,opt,name=kmem_charge_usage" json:"kmem_charge_usage,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemorySpec) Reset() { *m = MemorySpec{} } +func (m *MemorySpec) String() string { return proto.CompactTextString(m) } +func (*MemorySpec) ProtoMessage() {} + +func (m *MemorySpec) GetEvictionPriority() int32 { + if m != nil && m.EvictionPriority != nil { + return *m.EvictionPriority + } + return 0 +} + +func (m *MemorySpec) GetLimit() int64 { + if m != nil && m.Limit != nil { + return *m.Limit + } + return 0 +} + +func (m *MemorySpec) GetMaxLimit() int64 { + if m != nil && m.MaxLimit != nil { + return *m.MaxLimit + } + return 0 +} + +func (m *MemorySpec) GetReservation() int64 { + if m != nil && m.Reservation != nil { + return *m.Reservation + } + return 0 +} + +func (m *MemorySpec) GetHugetlbfsPath() string { + if m != nil && m.HugetlbfsPath != nil { + return *m.HugetlbfsPath + } + return "" +} + +func (m *MemorySpec) GetTmpfs() *MemorySpec_TmpfsSpec { + if m != nil { + return m.Tmpfs + } + return nil +} + +func (m *MemorySpec) GetSwapLimit() int64 { + if m != nil && m.SwapLimit != nil { + return *m.SwapLimit + } + return 0 +} + +func (m *MemorySpec) GetCompressionSamplingRatio() int32 { + if m != nil && m.CompressionSamplingRatio != nil { + return *m.CompressionSamplingRatio + } + return 0 +} + +func (m *MemorySpec) GetStalePageAge() int32 { + if m != nil && m.StalePageAge != nil { + return *m.StalePageAge + } + return 0 +} + +func (m *MemorySpec) GetDirty() *MemorySpec_Dirty { + if m != nil { + return m.Dirty + } + return nil +} + +func (m *MemorySpec) GetKmemChargeUsage() bool { + if m != nil && m.KmemChargeUsage != nil { + return *m.KmemChargeUsage + } + return false +} + +type MemorySpec_TmpfsSpec struct { + Path []string `protobuf:"bytes,1,rep,name=path" json:"path,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemorySpec_TmpfsSpec) Reset() { *m = MemorySpec_TmpfsSpec{} } +func (m *MemorySpec_TmpfsSpec) String() string { return proto.CompactTextString(m) } +func (*MemorySpec_TmpfsSpec) ProtoMessage() {} + +func (m *MemorySpec_TmpfsSpec) GetPath() []string { + if m != nil { + return m.Path + } + return nil +} + +type MemorySpec_Dirty struct { + Ratio *int32 `protobuf:"varint,1,opt,name=ratio" json:"ratio,omitempty"` + Limit *int32 `protobuf:"varint,2,opt,name=limit" json:"limit,omitempty"` + BackgroundRatio *int32 `protobuf:"varint,3,opt,name=background_ratio" json:"background_ratio,omitempty"` + BackgroundLimit *int32 `protobuf:"varint,4,opt,name=background_limit" json:"background_limit,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemorySpec_Dirty) Reset() { *m = MemorySpec_Dirty{} } +func (m *MemorySpec_Dirty) String() string { return proto.CompactTextString(m) } +func (*MemorySpec_Dirty) ProtoMessage() {} + +func (m *MemorySpec_Dirty) GetRatio() int32 { + if m != nil && m.Ratio != nil { + return *m.Ratio + } + return 0 +} + +func (m *MemorySpec_Dirty) GetLimit() int32 { + if m != nil && m.Limit != nil { + return *m.Limit + } + return 0 +} + +func (m *MemorySpec_Dirty) GetBackgroundRatio() int32 { + if m != nil && m.BackgroundRatio != nil { + return *m.BackgroundRatio + } + return 0 +} + +func (m *MemorySpec_Dirty) GetBackgroundLimit() int32 { + if m != nil && m.BackgroundLimit != nil { + return *m.BackgroundLimit + } + return 0 +} + +type BlockIoSpec struct { + DeviceLimitSet *BlockIoSpec_DeviceLimitSet `protobuf:"bytes,1,opt,name=device_limit_set" json:"device_limit_set,omitempty"` + MaxDeviceLimitSet *BlockIoSpec_MaxLimitSet `protobuf:"bytes,2,opt,name=max_device_limit_set" json:"max_device_limit_set,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *BlockIoSpec) Reset() { *m = BlockIoSpec{} } +func (m *BlockIoSpec) String() string { return proto.CompactTextString(m) } +func (*BlockIoSpec) ProtoMessage() {} + +func (m *BlockIoSpec) GetDeviceLimitSet() *BlockIoSpec_DeviceLimitSet { + if m != nil { + return m.DeviceLimitSet + } + return nil +} + +func (m *BlockIoSpec) GetMaxDeviceLimitSet() *BlockIoSpec_MaxLimitSet { + if m != nil { + return m.MaxDeviceLimitSet + } + return nil +} + +type BlockIoSpec_Device struct { + Major *int64 `protobuf:"varint,1,opt,name=major" json:"major,omitempty"` + Minor *int64 `protobuf:"varint,2,opt,name=minor" json:"minor,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *BlockIoSpec_Device) Reset() { *m = BlockIoSpec_Device{} } +func (m *BlockIoSpec_Device) String() string { return proto.CompactTextString(m) } +func (*BlockIoSpec_Device) ProtoMessage() {} + +func (m *BlockIoSpec_Device) GetMajor() int64 { + if m != nil && m.Major != nil { + return *m.Major + } + return 0 +} + +func (m *BlockIoSpec_Device) GetMinor() int64 { + if m != nil && m.Minor != nil { + return *m.Minor + } + return 0 +} + +type BlockIoSpec_DeviceLimit struct { + Device *BlockIoSpec_Device `protobuf:"bytes,1,opt,name=device" json:"device,omitempty"` + Limit *uint64 `protobuf:"varint,2,opt,name=limit" json:"limit,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *BlockIoSpec_DeviceLimit) Reset() { *m = BlockIoSpec_DeviceLimit{} } +func (m *BlockIoSpec_DeviceLimit) String() string { return proto.CompactTextString(m) } +func (*BlockIoSpec_DeviceLimit) ProtoMessage() {} + +func (m *BlockIoSpec_DeviceLimit) GetDevice() *BlockIoSpec_Device { + if m != nil { + return m.Device + } + return nil +} + +func (m *BlockIoSpec_DeviceLimit) GetLimit() uint64 { + if m != nil && m.Limit != nil { + return *m.Limit + } + return 0 +} + +type BlockIoSpec_DeviceLimitSet struct { + DefaultLimit *uint32 `protobuf:"varint,1,opt,name=default_limit" json:"default_limit,omitempty"` + DeviceLimits []*BlockIoSpec_DeviceLimit `protobuf:"bytes,2,rep,name=device_limits" json:"device_limits,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *BlockIoSpec_DeviceLimitSet) Reset() { *m = BlockIoSpec_DeviceLimitSet{} } +func (m *BlockIoSpec_DeviceLimitSet) String() string { return proto.CompactTextString(m) } +func (*BlockIoSpec_DeviceLimitSet) ProtoMessage() {} + +func (m *BlockIoSpec_DeviceLimitSet) GetDefaultLimit() uint32 { + if m != nil && m.DefaultLimit != nil { + return *m.DefaultLimit + } + return 0 +} + +func (m *BlockIoSpec_DeviceLimitSet) GetDeviceLimits() []*BlockIoSpec_DeviceLimit { + if m != nil { + return m.DeviceLimits + } + return nil +} + +type BlockIoSpec_MaxLimit struct { + Limits []*BlockIoSpec_DeviceLimit `protobuf:"bytes,1,rep,name=limits" json:"limits,omitempty"` + OpType *BlockIoSpec_OpType `protobuf:"varint,2,opt,name=op_type,enum=containers.BlockIoSpec_OpType" json:"op_type,omitempty"` + LimitType *BlockIoSpec_LimitType `protobuf:"varint,3,opt,name=limit_type,enum=containers.BlockIoSpec_LimitType" json:"limit_type,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *BlockIoSpec_MaxLimit) Reset() { *m = BlockIoSpec_MaxLimit{} } +func (m *BlockIoSpec_MaxLimit) String() string { return proto.CompactTextString(m) } +func (*BlockIoSpec_MaxLimit) ProtoMessage() {} + +func (m *BlockIoSpec_MaxLimit) GetLimits() []*BlockIoSpec_DeviceLimit { + if m != nil { + return m.Limits + } + return nil +} + +func (m *BlockIoSpec_MaxLimit) GetOpType() BlockIoSpec_OpType { + if m != nil && m.OpType != nil { + return *m.OpType + } + return BlockIoSpec_READ +} + +func (m *BlockIoSpec_MaxLimit) GetLimitType() BlockIoSpec_LimitType { + if m != nil && m.LimitType != nil { + return *m.LimitType + } + return BlockIoSpec_BYTES_PER_SECOND +} + +type BlockIoSpec_MaxLimitSet struct { + MaxLimits []*BlockIoSpec_MaxLimit `protobuf:"bytes,1,rep,name=max_limits" json:"max_limits,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *BlockIoSpec_MaxLimitSet) Reset() { *m = BlockIoSpec_MaxLimitSet{} } +func (m *BlockIoSpec_MaxLimitSet) String() string { return proto.CompactTextString(m) } +func (*BlockIoSpec_MaxLimitSet) ProtoMessage() {} + +func (m *BlockIoSpec_MaxLimitSet) GetMaxLimits() []*BlockIoSpec_MaxLimit { + if m != nil { + return m.MaxLimits + } + return nil +} + +type NetworkSpec struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *NetworkSpec) Reset() { *m = NetworkSpec{} } +func (m *NetworkSpec) String() string { return proto.CompactTextString(m) } +func (*NetworkSpec) ProtoMessage() {} + +type MonitoringSpec struct { + EnablePerfCounters *bool `protobuf:"varint,1,opt,name=enable_perf_counters" json:"enable_perf_counters,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MonitoringSpec) Reset() { *m = MonitoringSpec{} } +func (m *MonitoringSpec) String() string { return proto.CompactTextString(m) } +func (*MonitoringSpec) ProtoMessage() {} + +func (m *MonitoringSpec) GetEnablePerfCounters() bool { + if m != nil && m.EnablePerfCounters != nil { + return *m.EnablePerfCounters + } + return false +} + +type FilesystemSpec struct { + FdLimit *uint64 `protobuf:"varint,1,opt,name=fd_limit" json:"fd_limit,omitempty"` + Rootfs *string `protobuf:"bytes,2,opt,name=rootfs" json:"rootfs,omitempty"` + Mounts *Mounts `protobuf:"bytes,3,opt,name=mounts" json:"mounts,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FilesystemSpec) Reset() { *m = FilesystemSpec{} } +func (m *FilesystemSpec) String() string { return proto.CompactTextString(m) } +func (*FilesystemSpec) ProtoMessage() {} + +func (m *FilesystemSpec) GetFdLimit() uint64 { + if m != nil && m.FdLimit != nil { + return *m.FdLimit + } + return 0 +} + +func (m *FilesystemSpec) GetRootfs() string { + if m != nil && m.Rootfs != nil { + return *m.Rootfs + } + return "" +} + +func (m *FilesystemSpec) GetMounts() *Mounts { + if m != nil { + return m.Mounts + } + return nil +} + +type DeviceSpec struct { + RestrictionsSet *DeviceSpec_DeviceRestrictionsSet `protobuf:"bytes,1,opt,name=restrictions_set" json:"restrictions_set,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DeviceSpec) Reset() { *m = DeviceSpec{} } +func (m *DeviceSpec) String() string { return proto.CompactTextString(m) } +func (*DeviceSpec) ProtoMessage() {} + +func (m *DeviceSpec) GetRestrictionsSet() *DeviceSpec_DeviceRestrictionsSet { + if m != nil { + return m.RestrictionsSet + } + return nil +} + +type DeviceSpec_DeviceRestrictions struct { + Permission *DeviceSpec_DevicePermission `protobuf:"varint,1,opt,name=permission,enum=containers.DeviceSpec_DevicePermission" json:"permission,omitempty"` + Type *DeviceSpec_DeviceType `protobuf:"varint,2,opt,name=type,enum=containers.DeviceSpec_DeviceType" json:"type,omitempty"` + Access []DeviceSpec_DeviceAccess `protobuf:"varint,3,rep,name=access,enum=containers.DeviceSpec_DeviceAccess" json:"access,omitempty"` + Major *int64 `protobuf:"varint,4,opt,name=major" json:"major,omitempty"` + Minor *int64 `protobuf:"varint,5,opt,name=minor" json:"minor,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DeviceSpec_DeviceRestrictions) Reset() { *m = DeviceSpec_DeviceRestrictions{} } +func (m *DeviceSpec_DeviceRestrictions) String() string { return proto.CompactTextString(m) } +func (*DeviceSpec_DeviceRestrictions) ProtoMessage() {} + +func (m *DeviceSpec_DeviceRestrictions) GetPermission() DeviceSpec_DevicePermission { + if m != nil && m.Permission != nil { + return *m.Permission + } + return DeviceSpec_ALLOW +} + +func (m *DeviceSpec_DeviceRestrictions) GetType() DeviceSpec_DeviceType { + if m != nil && m.Type != nil { + return *m.Type + } + return DeviceSpec_DEVICE_CHAR +} + +func (m *DeviceSpec_DeviceRestrictions) GetAccess() []DeviceSpec_DeviceAccess { + if m != nil { + return m.Access + } + return nil +} + +func (m *DeviceSpec_DeviceRestrictions) GetMajor() int64 { + if m != nil && m.Major != nil { + return *m.Major + } + return 0 +} + +func (m *DeviceSpec_DeviceRestrictions) GetMinor() int64 { + if m != nil && m.Minor != nil { + return *m.Minor + } + return 0 +} + +type DeviceSpec_DeviceRestrictionsSet struct { + Restrictions []*DeviceSpec_DeviceRestrictions `protobuf:"bytes,1,rep,name=restrictions" json:"restrictions,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DeviceSpec_DeviceRestrictionsSet) Reset() { *m = DeviceSpec_DeviceRestrictionsSet{} } +func (m *DeviceSpec_DeviceRestrictionsSet) String() string { return proto.CompactTextString(m) } +func (*DeviceSpec_DeviceRestrictionsSet) ProtoMessage() {} + +func (m *DeviceSpec_DeviceRestrictionsSet) GetRestrictions() []*DeviceSpec_DeviceRestrictions { + if m != nil { + return m.Restrictions + } + return nil +} + +type SecuritySpec struct { + ApparmorProfile *SecuritySpec_AppArmorProfile `protobuf:"bytes,1,opt,name=apparmor_profile" json:"apparmor_profile,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SecuritySpec) Reset() { *m = SecuritySpec{} } +func (m *SecuritySpec) String() string { return proto.CompactTextString(m) } +func (*SecuritySpec) ProtoMessage() {} + +func (m *SecuritySpec) GetApparmorProfile() *SecuritySpec_AppArmorProfile { + if m != nil { + return m.ApparmorProfile + } + return nil +} + +type SecuritySpec_AppArmorProfile struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SecuritySpec_AppArmorProfile) Reset() { *m = SecuritySpec_AppArmorProfile{} } +func (m *SecuritySpec_AppArmorProfile) String() string { return proto.CompactTextString(m) } +func (*SecuritySpec_AppArmorProfile) ProtoMessage() {} + +func (m *SecuritySpec_AppArmorProfile) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +type ContainerStats struct { + Cpu *CpuStats `protobuf:"bytes,1,opt,name=cpu" json:"cpu,omitempty"` + Memory *MemoryStats `protobuf:"bytes,2,opt,name=memory" json:"memory,omitempty"` + Network *NetworkStats `protobuf:"bytes,4,opt,name=network" json:"network,omitempty"` + Blockio *BlockIoStats `protobuf:"bytes,7,opt,name=blockio" json:"blockio,omitempty"` + Monitoring *MonitoringStats `protobuf:"bytes,5,opt,name=monitoring" json:"monitoring,omitempty"` + Filesystem *FilesystemStats `protobuf:"bytes,6,opt,name=filesystem" json:"filesystem,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ContainerStats) Reset() { *m = ContainerStats{} } +func (m *ContainerStats) String() string { return proto.CompactTextString(m) } +func (*ContainerStats) ProtoMessage() {} + +func (m *ContainerStats) GetCpu() *CpuStats { + if m != nil { + return m.Cpu + } + return nil +} + +func (m *ContainerStats) GetMemory() *MemoryStats { + if m != nil { + return m.Memory + } + return nil +} + +func (m *ContainerStats) GetNetwork() *NetworkStats { + if m != nil { + return m.Network + } + return nil +} + +func (m *ContainerStats) GetBlockio() *BlockIoStats { + if m != nil { + return m.Blockio + } + return nil +} + +func (m *ContainerStats) GetMonitoring() *MonitoringStats { + if m != nil { + return m.Monitoring + } + return nil +} + +func (m *ContainerStats) GetFilesystem() *FilesystemStats { + if m != nil { + return m.Filesystem + } + return nil +} + +type HistogramMap struct { + Type *CpuHistogramType `protobuf:"varint,1,req,name=type,enum=containers.CpuHistogramType" json:"type,omitempty"` + Stat []*HistogramMap_Bucket `protobuf:"bytes,2,rep,name=stat" json:"stat,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *HistogramMap) Reset() { *m = HistogramMap{} } +func (m *HistogramMap) String() string { return proto.CompactTextString(m) } +func (*HistogramMap) ProtoMessage() {} + +func (m *HistogramMap) GetType() CpuHistogramType { + if m != nil && m.Type != nil { + return *m.Type + } + return CpuHistogramType_SERVE +} + +func (m *HistogramMap) GetStat() []*HistogramMap_Bucket { + if m != nil { + return m.Stat + } + return nil +} + +type HistogramMap_Bucket struct { + Bucket *int32 `protobuf:"varint,1,req,name=bucket" json:"bucket,omitempty"` + Value *int64 `protobuf:"varint,2,req,name=value" json:"value,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *HistogramMap_Bucket) Reset() { *m = HistogramMap_Bucket{} } +func (m *HistogramMap_Bucket) String() string { return proto.CompactTextString(m) } +func (*HistogramMap_Bucket) ProtoMessage() {} + +func (m *HistogramMap_Bucket) GetBucket() int32 { + if m != nil && m.Bucket != nil { + return *m.Bucket + } + return 0 +} + +func (m *HistogramMap_Bucket) GetValue() int64 { + if m != nil && m.Value != nil { + return *m.Value + } + return 0 +} + +type ThrottlingData struct { + Periods *int64 `protobuf:"varint,1,opt,name=periods" json:"periods,omitempty"` + ThrottledPeriods *int64 `protobuf:"varint,2,opt,name=throttled_periods" json:"throttled_periods,omitempty"` + ThrottledTime *int64 `protobuf:"varint,3,opt,name=throttled_time" json:"throttled_time,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ThrottlingData) Reset() { *m = ThrottlingData{} } +func (m *ThrottlingData) String() string { return proto.CompactTextString(m) } +func (*ThrottlingData) ProtoMessage() {} + +func (m *ThrottlingData) GetPeriods() int64 { + if m != nil && m.Periods != nil { + return *m.Periods + } + return 0 +} + +func (m *ThrottlingData) GetThrottledPeriods() int64 { + if m != nil && m.ThrottledPeriods != nil { + return *m.ThrottledPeriods + } + return 0 +} + +func (m *ThrottlingData) GetThrottledTime() int64 { + if m != nil && m.ThrottledTime != nil { + return *m.ThrottledTime + } + return 0 +} + +type CpuStats struct { + Usage *CpuStats_Usage `protobuf:"bytes,1,opt,name=usage" json:"usage,omitempty"` + Load *int32 `protobuf:"varint,2,opt,name=load" json:"load,omitempty"` + ThrottlingData *ThrottlingData `protobuf:"bytes,3,opt,name=throttling_data" json:"throttling_data,omitempty"` + Histograms []*HistogramMap `protobuf:"bytes,4,rep,name=histograms" json:"histograms,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CpuStats) Reset() { *m = CpuStats{} } +func (m *CpuStats) String() string { return proto.CompactTextString(m) } +func (*CpuStats) ProtoMessage() {} + +func (m *CpuStats) GetUsage() *CpuStats_Usage { + if m != nil { + return m.Usage + } + return nil +} + +func (m *CpuStats) GetLoad() int32 { + if m != nil && m.Load != nil { + return *m.Load + } + return 0 +} + +func (m *CpuStats) GetThrottlingData() *ThrottlingData { + if m != nil { + return m.ThrottlingData + } + return nil +} + +func (m *CpuStats) GetHistograms() []*HistogramMap { + if m != nil { + return m.Histograms + } + return nil +} + +type CpuStats_Usage struct { + Total *uint64 `protobuf:"varint,1,opt,name=total" json:"total,omitempty"` + PerCpu []int64 `protobuf:"varint,2,rep,name=per_cpu" json:"per_cpu,omitempty"` + User *int64 `protobuf:"varint,3,opt,name=user" json:"user,omitempty"` + System *int64 `protobuf:"varint,4,opt,name=system" json:"system,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CpuStats_Usage) Reset() { *m = CpuStats_Usage{} } +func (m *CpuStats_Usage) String() string { return proto.CompactTextString(m) } +func (*CpuStats_Usage) ProtoMessage() {} + +func (m *CpuStats_Usage) GetTotal() uint64 { + if m != nil && m.Total != nil { + return *m.Total + } + return 0 +} + +func (m *CpuStats_Usage) GetPerCpu() []int64 { + if m != nil { + return m.PerCpu + } + return nil +} + +func (m *CpuStats_Usage) GetUser() int64 { + if m != nil && m.User != nil { + return *m.User + } + return 0 +} + +func (m *CpuStats_Usage) GetSystem() int64 { + if m != nil && m.System != nil { + return *m.System + } + return 0 +} + +type MemoryStats struct { + Limit *int64 `protobuf:"varint,1,opt,name=limit" json:"limit,omitempty"` + EffectiveLimit *int64 `protobuf:"varint,2,opt,name=effective_limit" json:"effective_limit,omitempty"` + Reservation *int64 `protobuf:"varint,3,opt,name=reservation" json:"reservation,omitempty"` + Usage *int64 `protobuf:"varint,4,opt,name=usage" json:"usage,omitempty"` + MaxUsage *int64 `protobuf:"varint,5,opt,name=max_usage" json:"max_usage,omitempty"` + WorkingSet *int64 `protobuf:"varint,6,opt,name=working_set" json:"working_set,omitempty"` + ContainerData *MemoryStats_MemoryData `protobuf:"bytes,7,opt,name=container_data" json:"container_data,omitempty"` + HierarchicalData *MemoryStats_MemoryData `protobuf:"bytes,8,opt,name=hierarchical_data" json:"hierarchical_data,omitempty"` + HierarchicalMemoryLimit *int64 `protobuf:"varint,9,opt,name=hierarchical_memory_limit" json:"hierarchical_memory_limit,omitempty"` + Numa *MemoryStats_NumaStats `protobuf:"bytes,10,opt,name=numa" json:"numa,omitempty"` + IdlePage *MemoryStats_IdlePageStats `protobuf:"bytes,11,opt,name=idle_page" json:"idle_page,omitempty"` + CompressionSampling *MemoryStats_CompressionSamplingStats `protobuf:"bytes,12,opt,name=compression_sampling" json:"compression_sampling,omitempty"` + FailCount *int64 `protobuf:"varint,13,opt,name=fail_count" json:"fail_count,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats) Reset() { *m = MemoryStats{} } +func (m *MemoryStats) String() string { return proto.CompactTextString(m) } +func (*MemoryStats) ProtoMessage() {} + +func (m *MemoryStats) GetLimit() int64 { + if m != nil && m.Limit != nil { + return *m.Limit + } + return 0 +} + +func (m *MemoryStats) GetEffectiveLimit() int64 { + if m != nil && m.EffectiveLimit != nil { + return *m.EffectiveLimit + } + return 0 +} + +func (m *MemoryStats) GetReservation() int64 { + if m != nil && m.Reservation != nil { + return *m.Reservation + } + return 0 +} + +func (m *MemoryStats) GetUsage() int64 { + if m != nil && m.Usage != nil { + return *m.Usage + } + return 0 +} + +func (m *MemoryStats) GetMaxUsage() int64 { + if m != nil && m.MaxUsage != nil { + return *m.MaxUsage + } + return 0 +} + +func (m *MemoryStats) GetWorkingSet() int64 { + if m != nil && m.WorkingSet != nil { + return *m.WorkingSet + } + return 0 +} + +func (m *MemoryStats) GetContainerData() *MemoryStats_MemoryData { + if m != nil { + return m.ContainerData + } + return nil +} + +func (m *MemoryStats) GetHierarchicalData() *MemoryStats_MemoryData { + if m != nil { + return m.HierarchicalData + } + return nil +} + +func (m *MemoryStats) GetHierarchicalMemoryLimit() int64 { + if m != nil && m.HierarchicalMemoryLimit != nil { + return *m.HierarchicalMemoryLimit + } + return 0 +} + +func (m *MemoryStats) GetNuma() *MemoryStats_NumaStats { + if m != nil { + return m.Numa + } + return nil +} + +func (m *MemoryStats) GetIdlePage() *MemoryStats_IdlePageStats { + if m != nil { + return m.IdlePage + } + return nil +} + +func (m *MemoryStats) GetCompressionSampling() *MemoryStats_CompressionSamplingStats { + if m != nil { + return m.CompressionSampling + } + return nil +} + +func (m *MemoryStats) GetFailCount() int64 { + if m != nil && m.FailCount != nil { + return *m.FailCount + } + return 0 +} + +type MemoryStats_MemoryData struct { + Cache *int64 `protobuf:"varint,1,opt,name=cache" json:"cache,omitempty"` + Rss *int64 `protobuf:"varint,2,opt,name=rss" json:"rss,omitempty"` + RssHuge *int64 `protobuf:"varint,3,opt,name=rss_huge" json:"rss_huge,omitempty"` + MappedFile *int64 `protobuf:"varint,4,opt,name=mapped_file" json:"mapped_file,omitempty"` + Pgpgin *int64 `protobuf:"varint,5,opt,name=pgpgin" json:"pgpgin,omitempty"` + Pgpgout *int64 `protobuf:"varint,6,opt,name=pgpgout" json:"pgpgout,omitempty"` + Pgfault *int64 `protobuf:"varint,7,opt,name=pgfault" json:"pgfault,omitempty"` + Pgmajfault *int64 `protobuf:"varint,8,opt,name=pgmajfault" json:"pgmajfault,omitempty"` + Dirty *int64 `protobuf:"varint,9,opt,name=dirty" json:"dirty,omitempty"` + Writeback *int64 `protobuf:"varint,10,opt,name=writeback" json:"writeback,omitempty"` + InactiveAnon *int64 `protobuf:"varint,11,opt,name=inactive_anon" json:"inactive_anon,omitempty"` + ActiveAnon *int64 `protobuf:"varint,12,opt,name=active_anon" json:"active_anon,omitempty"` + InactiveFile *int64 `protobuf:"varint,13,opt,name=inactive_file" json:"inactive_file,omitempty"` + ActiveFile *int64 `protobuf:"varint,14,opt,name=active_file" json:"active_file,omitempty"` + Unevictable *int64 `protobuf:"varint,15,opt,name=unevictable" json:"unevictable,omitempty"` + Thp *MemoryStats_MemoryData_THP `protobuf:"bytes,16,opt,name=thp" json:"thp,omitempty"` + Kernel *MemoryStats_MemoryData_Kernel `protobuf:"bytes,17,opt,name=kernel" json:"kernel,omitempty"` + KernelNoncharged *MemoryStats_MemoryData_Kernel `protobuf:"bytes,18,opt,name=kernel_noncharged" json:"kernel_noncharged,omitempty"` + CompressedPoolPages *int64 `protobuf:"varint,19,opt,name=compressed_pool_pages" json:"compressed_pool_pages,omitempty"` + CompressedStoredPages *int64 `protobuf:"varint,20,opt,name=compressed_stored_pages" json:"compressed_stored_pages,omitempty"` + CompressedRejectCompressPoor *int64 `protobuf:"varint,21,opt,name=compressed_reject_compress_poor" json:"compressed_reject_compress_poor,omitempty"` + ZswapZsmallocFail *int64 `protobuf:"varint,22,opt,name=zswap_zsmalloc_fail" json:"zswap_zsmalloc_fail,omitempty"` + ZswapKmemcacheFail *int64 `protobuf:"varint,23,opt,name=zswap_kmemcache_fail" json:"zswap_kmemcache_fail,omitempty"` + ZswapDuplicateEntry *int64 `protobuf:"varint,24,opt,name=zswap_duplicate_entry" json:"zswap_duplicate_entry,omitempty"` + ZswapCompressedPages *int64 `protobuf:"varint,25,opt,name=zswap_compressed_pages" json:"zswap_compressed_pages,omitempty"` + ZswapDecompressedPages *int64 `protobuf:"varint,26,opt,name=zswap_decompressed_pages" json:"zswap_decompressed_pages,omitempty"` + ZswapCompressionNsec *int64 `protobuf:"varint,27,opt,name=zswap_compression_nsec" json:"zswap_compression_nsec,omitempty"` + ZswapDecompressionNsec *int64 `protobuf:"varint,28,opt,name=zswap_decompression_nsec" json:"zswap_decompression_nsec,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_MemoryData) Reset() { *m = MemoryStats_MemoryData{} } +func (m *MemoryStats_MemoryData) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_MemoryData) ProtoMessage() {} + +func (m *MemoryStats_MemoryData) GetCache() int64 { + if m != nil && m.Cache != nil { + return *m.Cache + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetRss() int64 { + if m != nil && m.Rss != nil { + return *m.Rss + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetRssHuge() int64 { + if m != nil && m.RssHuge != nil { + return *m.RssHuge + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetMappedFile() int64 { + if m != nil && m.MappedFile != nil { + return *m.MappedFile + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetPgpgin() int64 { + if m != nil && m.Pgpgin != nil { + return *m.Pgpgin + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetPgpgout() int64 { + if m != nil && m.Pgpgout != nil { + return *m.Pgpgout + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetPgfault() int64 { + if m != nil && m.Pgfault != nil { + return *m.Pgfault + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetPgmajfault() int64 { + if m != nil && m.Pgmajfault != nil { + return *m.Pgmajfault + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetDirty() int64 { + if m != nil && m.Dirty != nil { + return *m.Dirty + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetWriteback() int64 { + if m != nil && m.Writeback != nil { + return *m.Writeback + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetInactiveAnon() int64 { + if m != nil && m.InactiveAnon != nil { + return *m.InactiveAnon + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetActiveAnon() int64 { + if m != nil && m.ActiveAnon != nil { + return *m.ActiveAnon + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetInactiveFile() int64 { + if m != nil && m.InactiveFile != nil { + return *m.InactiveFile + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetActiveFile() int64 { + if m != nil && m.ActiveFile != nil { + return *m.ActiveFile + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetUnevictable() int64 { + if m != nil && m.Unevictable != nil { + return *m.Unevictable + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetThp() *MemoryStats_MemoryData_THP { + if m != nil { + return m.Thp + } + return nil +} + +func (m *MemoryStats_MemoryData) GetKernel() *MemoryStats_MemoryData_Kernel { + if m != nil { + return m.Kernel + } + return nil +} + +func (m *MemoryStats_MemoryData) GetKernelNoncharged() *MemoryStats_MemoryData_Kernel { + if m != nil { + return m.KernelNoncharged + } + return nil +} + +func (m *MemoryStats_MemoryData) GetCompressedPoolPages() int64 { + if m != nil && m.CompressedPoolPages != nil { + return *m.CompressedPoolPages + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetCompressedStoredPages() int64 { + if m != nil && m.CompressedStoredPages != nil { + return *m.CompressedStoredPages + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetCompressedRejectCompressPoor() int64 { + if m != nil && m.CompressedRejectCompressPoor != nil { + return *m.CompressedRejectCompressPoor + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetZswapZsmallocFail() int64 { + if m != nil && m.ZswapZsmallocFail != nil { + return *m.ZswapZsmallocFail + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetZswapKmemcacheFail() int64 { + if m != nil && m.ZswapKmemcacheFail != nil { + return *m.ZswapKmemcacheFail + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetZswapDuplicateEntry() int64 { + if m != nil && m.ZswapDuplicateEntry != nil { + return *m.ZswapDuplicateEntry + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetZswapCompressedPages() int64 { + if m != nil && m.ZswapCompressedPages != nil { + return *m.ZswapCompressedPages + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetZswapDecompressedPages() int64 { + if m != nil && m.ZswapDecompressedPages != nil { + return *m.ZswapDecompressedPages + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetZswapCompressionNsec() int64 { + if m != nil && m.ZswapCompressionNsec != nil { + return *m.ZswapCompressionNsec + } + return 0 +} + +func (m *MemoryStats_MemoryData) GetZswapDecompressionNsec() int64 { + if m != nil && m.ZswapDecompressionNsec != nil { + return *m.ZswapDecompressionNsec + } + return 0 +} + +type MemoryStats_MemoryData_THP struct { + FaultAlloc *int64 `protobuf:"varint,1,opt,name=fault_alloc" json:"fault_alloc,omitempty"` + FaultFallback *int64 `protobuf:"varint,2,opt,name=fault_fallback" json:"fault_fallback,omitempty"` + CollapseAlloc *int64 `protobuf:"varint,3,opt,name=collapse_alloc" json:"collapse_alloc,omitempty"` + CollapseAllocFailed *int64 `protobuf:"varint,4,opt,name=collapse_alloc_failed" json:"collapse_alloc_failed,omitempty"` + Split *int64 `protobuf:"varint,5,opt,name=split" json:"split,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_MemoryData_THP) Reset() { *m = MemoryStats_MemoryData_THP{} } +func (m *MemoryStats_MemoryData_THP) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_MemoryData_THP) ProtoMessage() {} + +func (m *MemoryStats_MemoryData_THP) GetFaultAlloc() int64 { + if m != nil && m.FaultAlloc != nil { + return *m.FaultAlloc + } + return 0 +} + +func (m *MemoryStats_MemoryData_THP) GetFaultFallback() int64 { + if m != nil && m.FaultFallback != nil { + return *m.FaultFallback + } + return 0 +} + +func (m *MemoryStats_MemoryData_THP) GetCollapseAlloc() int64 { + if m != nil && m.CollapseAlloc != nil { + return *m.CollapseAlloc + } + return 0 +} + +func (m *MemoryStats_MemoryData_THP) GetCollapseAllocFailed() int64 { + if m != nil && m.CollapseAllocFailed != nil { + return *m.CollapseAllocFailed + } + return 0 +} + +func (m *MemoryStats_MemoryData_THP) GetSplit() int64 { + if m != nil && m.Split != nil { + return *m.Split + } + return 0 +} + +type MemoryStats_MemoryData_Kernel struct { + Memory *int64 `protobuf:"varint,1,opt,name=memory" json:"memory,omitempty"` + SlabMemory *int64 `protobuf:"varint,2,opt,name=slab_memory" json:"slab_memory,omitempty"` + StackMemory *int64 `protobuf:"varint,3,opt,name=stack_memory" json:"stack_memory,omitempty"` + PgtableMemory *int64 `protobuf:"varint,4,opt,name=pgtable_memory" json:"pgtable_memory,omitempty"` + VmallocMemory *int64 `protobuf:"varint,5,opt,name=vmalloc_memory" json:"vmalloc_memory,omitempty"` + MiscMemory *int64 `protobuf:"varint,6,opt,name=misc_memory" json:"misc_memory,omitempty"` + TargetedSlabMemory *int64 `protobuf:"varint,7,opt,name=targeted_slab_memory" json:"targeted_slab_memory,omitempty"` + CompressedMemory *int64 `protobuf:"varint,8,opt,name=compressed_memory" json:"compressed_memory,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_MemoryData_Kernel) Reset() { *m = MemoryStats_MemoryData_Kernel{} } +func (m *MemoryStats_MemoryData_Kernel) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_MemoryData_Kernel) ProtoMessage() {} + +func (m *MemoryStats_MemoryData_Kernel) GetMemory() int64 { + if m != nil && m.Memory != nil { + return *m.Memory + } + return 0 +} + +func (m *MemoryStats_MemoryData_Kernel) GetSlabMemory() int64 { + if m != nil && m.SlabMemory != nil { + return *m.SlabMemory + } + return 0 +} + +func (m *MemoryStats_MemoryData_Kernel) GetStackMemory() int64 { + if m != nil && m.StackMemory != nil { + return *m.StackMemory + } + return 0 +} + +func (m *MemoryStats_MemoryData_Kernel) GetPgtableMemory() int64 { + if m != nil && m.PgtableMemory != nil { + return *m.PgtableMemory + } + return 0 +} + +func (m *MemoryStats_MemoryData_Kernel) GetVmallocMemory() int64 { + if m != nil && m.VmallocMemory != nil { + return *m.VmallocMemory + } + return 0 +} + +func (m *MemoryStats_MemoryData_Kernel) GetMiscMemory() int64 { + if m != nil && m.MiscMemory != nil { + return *m.MiscMemory + } + return 0 +} + +func (m *MemoryStats_MemoryData_Kernel) GetTargetedSlabMemory() int64 { + if m != nil && m.TargetedSlabMemory != nil { + return *m.TargetedSlabMemory + } + return 0 +} + +func (m *MemoryStats_MemoryData_Kernel) GetCompressedMemory() int64 { + if m != nil && m.CompressedMemory != nil { + return *m.CompressedMemory + } + return 0 +} + +type MemoryStats_NumaStats struct { + ContainerData *MemoryStats_NumaStats_NumaData `protobuf:"bytes,1,opt,name=container_data" json:"container_data,omitempty"` + HierarchicalData *MemoryStats_NumaStats_NumaData `protobuf:"bytes,2,opt,name=hierarchical_data" json:"hierarchical_data,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_NumaStats) Reset() { *m = MemoryStats_NumaStats{} } +func (m *MemoryStats_NumaStats) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_NumaStats) ProtoMessage() {} + +func (m *MemoryStats_NumaStats) GetContainerData() *MemoryStats_NumaStats_NumaData { + if m != nil { + return m.ContainerData + } + return nil +} + +func (m *MemoryStats_NumaStats) GetHierarchicalData() *MemoryStats_NumaStats_NumaData { + if m != nil { + return m.HierarchicalData + } + return nil +} + +type MemoryStats_NumaStats_NumaData struct { + Total *MemoryStats_NumaStats_NumaData_Stat `protobuf:"bytes,1,opt,name=total" json:"total,omitempty"` + File *MemoryStats_NumaStats_NumaData_Stat `protobuf:"bytes,2,opt,name=file" json:"file,omitempty"` + Anon *MemoryStats_NumaStats_NumaData_Stat `protobuf:"bytes,3,opt,name=anon" json:"anon,omitempty"` + Unevictable *MemoryStats_NumaStats_NumaData_Stat `protobuf:"bytes,4,opt,name=unevictable" json:"unevictable,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_NumaStats_NumaData) Reset() { *m = MemoryStats_NumaStats_NumaData{} } +func (m *MemoryStats_NumaStats_NumaData) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_NumaStats_NumaData) ProtoMessage() {} + +func (m *MemoryStats_NumaStats_NumaData) GetTotal() *MemoryStats_NumaStats_NumaData_Stat { + if m != nil { + return m.Total + } + return nil +} + +func (m *MemoryStats_NumaStats_NumaData) GetFile() *MemoryStats_NumaStats_NumaData_Stat { + if m != nil { + return m.File + } + return nil +} + +func (m *MemoryStats_NumaStats_NumaData) GetAnon() *MemoryStats_NumaStats_NumaData_Stat { + if m != nil { + return m.Anon + } + return nil +} + +func (m *MemoryStats_NumaStats_NumaData) GetUnevictable() *MemoryStats_NumaStats_NumaData_Stat { + if m != nil { + return m.Unevictable + } + return nil +} + +type MemoryStats_NumaStats_NumaData_Stat struct { + Node []*MemoryStats_NumaStats_NumaData_Stat_Node `protobuf:"bytes,1,rep,name=node" json:"node,omitempty"` + TotalPageCount *int64 `protobuf:"varint,2,opt,name=total_page_count" json:"total_page_count,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_NumaStats_NumaData_Stat) Reset() { *m = MemoryStats_NumaStats_NumaData_Stat{} } +func (m *MemoryStats_NumaStats_NumaData_Stat) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_NumaStats_NumaData_Stat) ProtoMessage() {} + +func (m *MemoryStats_NumaStats_NumaData_Stat) GetNode() []*MemoryStats_NumaStats_NumaData_Stat_Node { + if m != nil { + return m.Node + } + return nil +} + +func (m *MemoryStats_NumaStats_NumaData_Stat) GetTotalPageCount() int64 { + if m != nil && m.TotalPageCount != nil { + return *m.TotalPageCount + } + return 0 +} + +type MemoryStats_NumaStats_NumaData_Stat_Node struct { + Level *int32 `protobuf:"varint,1,opt,name=level" json:"level,omitempty"` + PageCount *int64 `protobuf:"varint,2,opt,name=page_count" json:"page_count,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_NumaStats_NumaData_Stat_Node) Reset() { + *m = MemoryStats_NumaStats_NumaData_Stat_Node{} +} +func (m *MemoryStats_NumaStats_NumaData_Stat_Node) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_NumaStats_NumaData_Stat_Node) ProtoMessage() {} + +func (m *MemoryStats_NumaStats_NumaData_Stat_Node) GetLevel() int32 { + if m != nil && m.Level != nil { + return *m.Level + } + return 0 +} + +func (m *MemoryStats_NumaStats_NumaData_Stat_Node) GetPageCount() int64 { + if m != nil && m.PageCount != nil { + return *m.PageCount + } + return 0 +} + +type MemoryStats_IdlePageStats struct { + Stats []*MemoryStats_IdlePageStats_Stats `protobuf:"bytes,1,rep,name=stats" json:"stats,omitempty"` + Scans *int64 `protobuf:"varint,2,opt,name=scans" json:"scans,omitempty"` + Stale *int64 `protobuf:"varint,3,opt,name=stale" json:"stale,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_IdlePageStats) Reset() { *m = MemoryStats_IdlePageStats{} } +func (m *MemoryStats_IdlePageStats) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_IdlePageStats) ProtoMessage() {} + +func (m *MemoryStats_IdlePageStats) GetStats() []*MemoryStats_IdlePageStats_Stats { + if m != nil { + return m.Stats + } + return nil +} + +func (m *MemoryStats_IdlePageStats) GetScans() int64 { + if m != nil && m.Scans != nil { + return *m.Scans + } + return 0 +} + +func (m *MemoryStats_IdlePageStats) GetStale() int64 { + if m != nil && m.Stale != nil { + return *m.Stale + } + return 0 +} + +type MemoryStats_IdlePageStats_Stats struct { + AgeInSecs *int32 `protobuf:"varint,1,opt,name=age_in_secs" json:"age_in_secs,omitempty"` + Clean *int64 `protobuf:"varint,2,opt,name=clean" json:"clean,omitempty"` + DirtyFile *int64 `protobuf:"varint,3,opt,name=dirty_file" json:"dirty_file,omitempty"` + DirtySwap *int64 `protobuf:"varint,4,opt,name=dirty_swap" json:"dirty_swap,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_IdlePageStats_Stats) Reset() { *m = MemoryStats_IdlePageStats_Stats{} } +func (m *MemoryStats_IdlePageStats_Stats) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_IdlePageStats_Stats) ProtoMessage() {} + +func (m *MemoryStats_IdlePageStats_Stats) GetAgeInSecs() int32 { + if m != nil && m.AgeInSecs != nil { + return *m.AgeInSecs + } + return 0 +} + +func (m *MemoryStats_IdlePageStats_Stats) GetClean() int64 { + if m != nil && m.Clean != nil { + return *m.Clean + } + return 0 +} + +func (m *MemoryStats_IdlePageStats_Stats) GetDirtyFile() int64 { + if m != nil && m.DirtyFile != nil { + return *m.DirtyFile + } + return 0 +} + +func (m *MemoryStats_IdlePageStats_Stats) GetDirtySwap() int64 { + if m != nil && m.DirtySwap != nil { + return *m.DirtySwap + } + return 0 +} + +type MemoryStats_CompressionSamplingStats struct { + RawSize *int64 `protobuf:"varint,1,opt,name=raw_size" json:"raw_size,omitempty"` + CompressedSize *int64 `protobuf:"varint,2,opt,name=compressed_size" json:"compressed_size,omitempty"` + FifoOverflow *int64 `protobuf:"varint,3,opt,name=fifo_overflow" json:"fifo_overflow,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MemoryStats_CompressionSamplingStats) Reset() { *m = MemoryStats_CompressionSamplingStats{} } +func (m *MemoryStats_CompressionSamplingStats) String() string { return proto.CompactTextString(m) } +func (*MemoryStats_CompressionSamplingStats) ProtoMessage() {} + +func (m *MemoryStats_CompressionSamplingStats) GetRawSize() int64 { + if m != nil && m.RawSize != nil { + return *m.RawSize + } + return 0 +} + +func (m *MemoryStats_CompressionSamplingStats) GetCompressedSize() int64 { + if m != nil && m.CompressedSize != nil { + return *m.CompressedSize + } + return 0 +} + +func (m *MemoryStats_CompressionSamplingStats) GetFifoOverflow() int64 { + if m != nil && m.FifoOverflow != nil { + return *m.FifoOverflow + } + return 0 +} + +type BlockIoStats struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *BlockIoStats) Reset() { *m = BlockIoStats{} } +func (m *BlockIoStats) String() string { return proto.CompactTextString(m) } +func (*BlockIoStats) ProtoMessage() {} + +type NetworkStats struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *NetworkStats) Reset() { *m = NetworkStats{} } +func (m *NetworkStats) String() string { return proto.CompactTextString(m) } +func (*NetworkStats) ProtoMessage() {} + +type MonitoringStats struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *MonitoringStats) Reset() { *m = MonitoringStats{} } +func (m *MonitoringStats) String() string { return proto.CompactTextString(m) } +func (*MonitoringStats) ProtoMessage() {} + +type FilesystemStats struct { + FdUsage *int64 `protobuf:"varint,1,opt,name=fd_usage" json:"fd_usage,omitempty"` + FdMaxUsage *int64 `protobuf:"varint,2,opt,name=fd_max_usage" json:"fd_max_usage,omitempty"` + FdFailCount *int64 `protobuf:"varint,3,opt,name=fd_fail_count" json:"fd_fail_count,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FilesystemStats) Reset() { *m = FilesystemStats{} } +func (m *FilesystemStats) String() string { return proto.CompactTextString(m) } +func (*FilesystemStats) ProtoMessage() {} + +func (m *FilesystemStats) GetFdUsage() int64 { + if m != nil && m.FdUsage != nil { + return *m.FdUsage + } + return 0 +} + +func (m *FilesystemStats) GetFdMaxUsage() int64 { + if m != nil && m.FdMaxUsage != nil { + return *m.FdMaxUsage + } + return 0 +} + +func (m *FilesystemStats) GetFdFailCount() int64 { + if m != nil && m.FdFailCount != nil { + return *m.FdFailCount + } + return 0 +} + +func init() { + proto.RegisterEnum("containers.SchedulingLatency", SchedulingLatency_name, SchedulingLatency_value) + proto.RegisterEnum("containers.CpuHistogramType", CpuHistogramType_name, CpuHistogramType_value) + proto.RegisterEnum("containers.RunSpec_FdPolicy", RunSpec_FdPolicy_name, RunSpec_FdPolicy_value) + proto.RegisterEnum("containers.BlockIoSpec_OpType", BlockIoSpec_OpType_name, BlockIoSpec_OpType_value) + proto.RegisterEnum("containers.BlockIoSpec_LimitType", BlockIoSpec_LimitType_name, BlockIoSpec_LimitType_value) + proto.RegisterEnum("containers.DeviceSpec_DeviceType", DeviceSpec_DeviceType_name, DeviceSpec_DeviceType_value) + proto.RegisterEnum("containers.DeviceSpec_DeviceAccess", DeviceSpec_DeviceAccess_name, DeviceSpec_DeviceAccess_value) + proto.RegisterEnum("containers.DeviceSpec_DevicePermission", DeviceSpec_DevicePermission_name, DeviceSpec_DevicePermission_value) +} diff --git a/container/lmctfy/lmctfy_container.go b/container/lmctfy/lmctfy_container.go new file mode 100644 index 00000000..d0d33860 --- /dev/null +++ b/container/lmctfy/lmctfy_container.go @@ -0,0 +1,195 @@ +// Copyright 2014 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. + +// A container object + +package lmctfy + +import ( + "fmt" + "os/exec" + "strings" + "syscall" + "time" + + "code.google.com/p/goprotobuf/proto" + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/info" +) + +type lmctfyContainerHandler struct { + // Container name + Name string + container.NoStatsSummary +} + +const ( + lmctfyBinary = "lmctfy" + notFoundExitCode = 5 +) + +// Create a new +func New(name string) (container.ContainerHandler, error) { + el := &lmctfyContainerHandler{ + Name: name, + } + return el, nil +} + +func getExitCode(err error) int { + msg, ok := err.(*exec.ExitError) + if ok { + return msg.Sys().(syscall.WaitStatus).ExitStatus() + } + return -1 +} + +func protobufToContainerSpec(pspec *ContainerSpec) *info.ContainerSpec { + ret := new(info.ContainerSpec) + if pspec.GetCpu() != nil { + cpuspec := new(info.CpuSpec) + cpuspec.Limit = pspec.GetCpu().GetLimit() + cpuspec.MaxLimit = pspec.GetCpu().GetMaxLimit() + if pspec.GetCpu().GetMask() != nil { + cpuspec.Mask.Data = pspec.GetCpu().GetMask().GetData() + } + ret.Cpu = cpuspec + } + if pspec.GetMemory() != nil { + pmem := pspec.GetMemory() + memspec := new(info.MemorySpec) + memspec.Limit = uint64(pmem.GetLimit()) + memspec.Reservation = uint64(pmem.GetReservation()) + memspec.SwapLimit = uint64(pmem.GetSwapLimit()) + ret.Memory = memspec + } + return ret +} + +// Gets spec. +func (c *lmctfyContainerHandler) GetSpec() (*info.ContainerSpec, error) { + // Run lmctfy spec "container_name" and get spec. + // Ignore if the container was not found. + cmd := exec.Command(lmctfyBinary, "spec", string(c.Name)) + data, err := cmd.Output() + if err != nil && getExitCode(err) != notFoundExitCode { + return nil, fmt.Errorf("unable to run command %v spec %v: %v", lmctfyBinary, c.Name, err) + } + + // Parse output into a protobuf. + pspec := &ContainerSpec{} + err = proto.UnmarshalText(string(data), pspec) + if err != nil { + return nil, err + } + spec := protobufToContainerSpec(pspec) + return spec, nil +} + +func protobufToMemoryData(pmd *MemoryStats_MemoryData, data *info.MemoryStatsMemoryData) { + if pmd == nil { + return + } + data.Pgfault = uint64(pmd.GetPgfault()) + data.Pgmajfault = uint64(pmd.GetPgmajfault()) + return +} + +func protobufToContainerStats(pstats *ContainerStats) *info.ContainerStats { + ret := new(info.ContainerStats) + if pstats.GetCpu() != nil { + pcpu := pstats.GetCpu() + cpustats := new(info.CpuStats) + cpustats.Usage.Total = pcpu.GetUsage().GetTotal() + percpu := pcpu.GetUsage().GetPerCpu() + if len(percpu) > 0 { + cpustats.Usage.PerCpu = make([]uint64, len(percpu)) + for i, p := range percpu { + cpustats.Usage.PerCpu[i] = uint64(p) + } + } + cpustats.Usage.User = uint64(pcpu.GetUsage().GetUser()) + cpustats.Usage.System = uint64(pcpu.GetUsage().GetSystem()) + cpustats.Load = pcpu.GetLoad() + ret.Cpu = cpustats + } + if pstats.GetMemory() != nil { + pmem := pstats.GetMemory() + memstats := new(info.MemoryStats) + memstats.Limit = uint64(pmem.GetLimit()) + memstats.Usage = uint64(pmem.GetUsage()) + protobufToMemoryData(pmem.GetContainerData(), &memstats.ContainerData) + protobufToMemoryData(pmem.GetHierarchicalData(), &memstats.HierarchicalData) + ret.Memory = memstats + } + return ret +} + +// Gets full stats. +func (c *lmctfyContainerHandler) GetStats() (*info.ContainerStats, error) { + // Ignore if the container was not found. + cmd := exec.Command(lmctfyBinary, "stats", "full", string(c.Name)) + data, err := cmd.Output() + if err != nil && getExitCode(err) != notFoundExitCode { + return nil, fmt.Errorf("unable to run command %v stats full %v: %v", lmctfyBinary, c.Name, err) + } + + // Parse output into a protobuf. + pstats := &ContainerStats{} + err = proto.UnmarshalText(string(data), pstats) + if err != nil { + return nil, err + } + stats := protobufToContainerStats(pstats) + stats.Timestamp = time.Now() + return stats, nil +} + +// Gets all subcontainers. +func (c *lmctfyContainerHandler) ListContainers(listType container.ListType) ([]string, error) { + // Prepare the arguments. + args := []string{"list", "containers", "-v"} + if listType == container.LIST_RECURSIVE { + args = append(args, "-r") + } + args = append(args, c.Name) + + // Run the command. + cmd := exec.Command(lmctfyBinary, args...) + data, err := cmd.Output() + if err != nil && getExitCode(err) != notFoundExitCode { + return nil, err + } + + // Parse lines as container names. + if len(data) == 0 { + return []string{}, nil + } + names := strings.Split(string(data), "\n") + containerNames := make([]string, 0, len(names)) + for _, name := range names { + if len(name) != 0 { + containerNames = append(containerNames, name) + } + } + return containerNames, nil +} + +// TODO(vmarmol): Implement +func (c *lmctfyContainerHandler) ListThreads(listType container.ListType) ([]int, error) { + return []int{}, nil +} +func (c *lmctfyContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { + return []int{}, nil +} diff --git a/container/lmctfy/virtual_host.pb.go b/container/lmctfy/virtual_host.pb.go new file mode 100644 index 00000000..7c2f1485 --- /dev/null +++ b/container/lmctfy/virtual_host.pb.go @@ -0,0 +1,273 @@ +// Copyright 2014 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. + +// Code generated by protoc-gen-go. +// source: virtual_host.proto +// DO NOT EDIT! + +package lmctfy + +import proto "code.google.com/p/goprotobuf/proto" +import json "encoding/json" +import math "math" + +// Reference proto, json, and math imports to suppress error if they are not otherwise used. +var _ = proto.Marshal +var _ = &json.SyntaxError{} +var _ = math.Inf + +type Network_Bridge_Type int32 + +const ( + Network_Bridge_ETH Network_Bridge_Type = 0 + Network_Bridge_OVS Network_Bridge_Type = 1 +) + +var Network_Bridge_Type_name = map[int32]string{ + 0: "ETH", + 1: "OVS", +} +var Network_Bridge_Type_value = map[string]int32{ + "ETH": 0, + "OVS": 1, +} + +func (x Network_Bridge_Type) Enum() *Network_Bridge_Type { + p := new(Network_Bridge_Type) + *p = x + return p +} +func (x Network_Bridge_Type) String() string { + return proto.EnumName(Network_Bridge_Type_name, int32(x)) +} +func (x *Network_Bridge_Type) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Network_Bridge_Type_value, data, "Network_Bridge_Type") + if err != nil { + return err + } + *x = Network_Bridge_Type(value) + return nil +} + +type Network struct { + Interface *string `protobuf:"bytes,1,opt,name=interface" json:"interface,omitempty"` + Connection *Network_Connection `protobuf:"bytes,3,opt,name=connection" json:"connection,omitempty"` + VirtualIp *Network_VirtualIp `protobuf:"bytes,2,opt,name=virtual_ip" json:"virtual_ip,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Network) Reset() { *m = Network{} } +func (m *Network) String() string { return proto.CompactTextString(m) } +func (*Network) ProtoMessage() {} + +func (m *Network) GetInterface() string { + if m != nil && m.Interface != nil { + return *m.Interface + } + return "" +} + +func (m *Network) GetConnection() *Network_Connection { + if m != nil { + return m.Connection + } + return nil +} + +func (m *Network) GetVirtualIp() *Network_VirtualIp { + if m != nil { + return m.VirtualIp + } + return nil +} + +type Network_VethPair struct { + Outside *string `protobuf:"bytes,1,opt,name=outside" json:"outside,omitempty"` + Inside *string `protobuf:"bytes,2,opt,name=inside" json:"inside,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Network_VethPair) Reset() { *m = Network_VethPair{} } +func (m *Network_VethPair) String() string { return proto.CompactTextString(m) } +func (*Network_VethPair) ProtoMessage() {} + +func (m *Network_VethPair) GetOutside() string { + if m != nil && m.Outside != nil { + return *m.Outside + } + return "" +} + +func (m *Network_VethPair) GetInside() string { + if m != nil && m.Inside != nil { + return *m.Inside + } + return "" +} + +type Network_Bridge struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Type *Network_Bridge_Type `protobuf:"varint,2,opt,name=type,enum=containers.Network_Bridge_Type" json:"type,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Network_Bridge) Reset() { *m = Network_Bridge{} } +func (m *Network_Bridge) String() string { return proto.CompactTextString(m) } +func (*Network_Bridge) ProtoMessage() {} + +func (m *Network_Bridge) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *Network_Bridge) GetType() Network_Bridge_Type { + if m != nil && m.Type != nil { + return *m.Type + } + return Network_Bridge_ETH +} + +type Network_Connection struct { + VethPair *Network_VethPair `protobuf:"bytes,1,opt,name=veth_pair" json:"veth_pair,omitempty"` + Bridge *Network_Bridge `protobuf:"bytes,2,opt,name=bridge" json:"bridge,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Network_Connection) Reset() { *m = Network_Connection{} } +func (m *Network_Connection) String() string { return proto.CompactTextString(m) } +func (*Network_Connection) ProtoMessage() {} + +func (m *Network_Connection) GetVethPair() *Network_VethPair { + if m != nil { + return m.VethPair + } + return nil +} + +func (m *Network_Connection) GetBridge() *Network_Bridge { + if m != nil { + return m.Bridge + } + return nil +} + +type Network_VirtualIp struct { + Ip *string `protobuf:"bytes,1,opt,name=ip" json:"ip,omitempty"` + Netmask *string `protobuf:"bytes,2,opt,name=netmask" json:"netmask,omitempty"` + Gateway *string `protobuf:"bytes,3,opt,name=gateway" json:"gateway,omitempty"` + Mtu *int32 `protobuf:"varint,4,opt,name=mtu" json:"mtu,omitempty"` + IpForward *bool `protobuf:"varint,5,opt,name=ip_forward" json:"ip_forward,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Network_VirtualIp) Reset() { *m = Network_VirtualIp{} } +func (m *Network_VirtualIp) String() string { return proto.CompactTextString(m) } +func (*Network_VirtualIp) ProtoMessage() {} + +func (m *Network_VirtualIp) GetIp() string { + if m != nil && m.Ip != nil { + return *m.Ip + } + return "" +} + +func (m *Network_VirtualIp) GetNetmask() string { + if m != nil && m.Netmask != nil { + return *m.Netmask + } + return "" +} + +func (m *Network_VirtualIp) GetGateway() string { + if m != nil && m.Gateway != nil { + return *m.Gateway + } + return "" +} + +func (m *Network_VirtualIp) GetMtu() int32 { + if m != nil && m.Mtu != nil { + return *m.Mtu + } + return 0 +} + +func (m *Network_VirtualIp) GetIpForward() bool { + if m != nil && m.IpForward != nil { + return *m.IpForward + } + return false +} + +type Mounts struct { + Mount []*Mounts_Mount `protobuf:"bytes,1,rep,name=mount" json:"mount,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Mounts) Reset() { *m = Mounts{} } +func (m *Mounts) String() string { return proto.CompactTextString(m) } +func (*Mounts) ProtoMessage() {} + +func (m *Mounts) GetMount() []*Mounts_Mount { + if m != nil { + return m.Mount + } + return nil +} + +type Mounts_Mount struct { + Source *string `protobuf:"bytes,1,opt,name=source" json:"source,omitempty"` + Target *string `protobuf:"bytes,2,opt,name=target" json:"target,omitempty"` + ReadOnly *bool `protobuf:"varint,3,opt,name=read_only" json:"read_only,omitempty"` + Private *bool `protobuf:"varint,4,opt,name=private" json:"private,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Mounts_Mount) Reset() { *m = Mounts_Mount{} } +func (m *Mounts_Mount) String() string { return proto.CompactTextString(m) } +func (*Mounts_Mount) ProtoMessage() {} + +func (m *Mounts_Mount) GetSource() string { + if m != nil && m.Source != nil { + return *m.Source + } + return "" +} + +func (m *Mounts_Mount) GetTarget() string { + if m != nil && m.Target != nil { + return *m.Target + } + return "" +} + +func (m *Mounts_Mount) GetReadOnly() bool { + if m != nil && m.ReadOnly != nil { + return *m.ReadOnly + } + return false +} + +func (m *Mounts_Mount) GetPrivate() bool { + if m != nil && m.Private != nil { + return *m.Private + } + return false +} + +func init() { + proto.RegisterEnum("containers.Network_Bridge_Type", Network_Bridge_Type_name, Network_Bridge_Type_value) +} diff --git a/container/samplermngr.go b/container/samplermngr.go new file mode 100644 index 00000000..a290036c --- /dev/null +++ b/container/samplermngr.go @@ -0,0 +1,101 @@ +package container + +import ( + "fmt" + "sync" + "time" + + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/sampling" +) + +type samplerFactor interface { + String() string + NewSampler(*StatsParameter) (sampling.Sampler, error) +} + +type samplerManager struct { + factoryMap map[string]samplerFactor + lock sync.RWMutex +} + +func (self *samplerManager) Register(factory samplerFactor) { + self.lock.Lock() + defer self.lock.Unlock() + + if self.factoryMap == nil { + self.factoryMap = make(map[string]samplerFactor, 3) + } + self.factoryMap[factory.String()] = factory +} + +func (self *samplerManager) NewSampler(param *StatsParameter) (sampling.Sampler, error) { + self.lock.RLock() + defer self.lock.RUnlock() + if f, ok := self.factoryMap[param.Sampler]; ok { + return f.NewSampler(param) + } + return nil, fmt.Errorf("unknown sampler %v", param.Sampler) +} + +var globalSamplerManager samplerManager + +func NewSampler(param *StatsParameter) (sampling.Sampler, error) { + return globalSamplerManager.NewSampler(param) +} + +type reservoirSamplerFactory struct { +} + +func (self *reservoirSamplerFactory) String() string { + return "uniform" +} + +func (self *reservoirSamplerFactory) NewSampler(param *StatsParameter) (sampling.Sampler, error) { + s := sampling.NewReservoirSampler(param.NumSamples) + if param.ResetPeriod.Seconds() > 1.0 { + s = sampling.NewPeriodcallyResetSampler(param.ResetPeriod, s) + } + return s, nil +} + +type esSamplerFactory struct { + startTime time.Time +} + +func (self *esSamplerFactory) String() string { + return "weighted" +} + +func (self *esSamplerFactory) NewSampler(param *StatsParameter) (sampling.Sampler, error) { + s := sampling.NewESSampler(param.NumSamples, func(d interface{}) float64 { + stats := d.(*info.ContainerStats) + delta := self.startTime.Sub(stats.Timestamp) + return delta.Seconds() + }) + if param.ResetPeriod.Seconds() > 1.0 { + s = sampling.NewPeriodcallyResetSampler(param.ResetPeriod, s) + } + return s, nil +} + +type chainSamplerFactory struct { +} + +func (self *chainSamplerFactory) String() string { + return "window" +} + +func (self *chainSamplerFactory) NewSampler(param *StatsParameter) (sampling.Sampler, error) { + s := sampling.NewChainSampler(param.NumSamples, param.WindowSize) + if param.ResetPeriod.Seconds() > 1.0 { + s = sampling.NewPeriodcallyResetSampler(param.ResetPeriod, s) + } + return s, nil +} + +func init() { + globalSamplerManager.Register(&reservoirSamplerFactory{}) + globalSamplerManager.Register(&esSamplerFactory{time.Now()}) + globalSamplerManager.Register(&chainSamplerFactory{}) +} diff --git a/container/statssum.go b/container/statssum.go new file mode 100644 index 00000000..6126b877 --- /dev/null +++ b/container/statssum.go @@ -0,0 +1,120 @@ +// Copyright 2014 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 container + +import ( + "math/big" + "sync" + "time" + + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/sampling" +) + +type statsSummaryContainerHandlerWrapper struct { + handler ContainerHandler + currentSummary *info.ContainerStatsSummary + totalMemoryUsage *big.Int + numStats uint64 + sampler sampling.Sampler + lock sync.Mutex +} + +func (self *statsSummaryContainerHandlerWrapper) GetSpec() (*info.ContainerSpec, error) { + return self.handler.GetSpec() +} + +func (self *statsSummaryContainerHandlerWrapper) GetStats() (*info.ContainerStats, error) { + stats, err := self.handler.GetStats() + if err != nil { + return nil, err + } + if stats == nil { + return nil, nil + } + stats.Timestamp = time.Now() + self.lock.Lock() + defer self.lock.Unlock() + + self.sampler.Update(stats) + if self.currentSummary == nil { + self.currentSummary = new(info.ContainerStatsSummary) + } + self.numStats++ + if stats.Memory != nil { + if stats.Memory.Usage > self.currentSummary.MaxMemoryUsage { + self.currentSummary.MaxMemoryUsage = stats.Memory.Usage + } + + // XXX(dengnan): Very inefficient! + if self.totalMemoryUsage == nil { + self.totalMemoryUsage = new(big.Int) + } + usage := (&big.Int{}).SetUint64(stats.Memory.Usage) + self.totalMemoryUsage = self.totalMemoryUsage.Add(self.totalMemoryUsage, usage) + n := (&big.Int{}).SetUint64(self.numStats) + avg := (&big.Int{}).Div(self.totalMemoryUsage, n) + self.currentSummary.AvgMemoryUsage = avg.Uint64() + } + return stats, nil +} + +func (self *statsSummaryContainerHandlerWrapper) ListContainers(listType ListType) ([]string, error) { + return self.handler.ListContainers(listType) +} + +func (self *statsSummaryContainerHandlerWrapper) ListThreads(listType ListType) ([]int, error) { + return self.handler.ListThreads(listType) +} + +func (self *statsSummaryContainerHandlerWrapper) ListProcesses(listType ListType) ([]int, error) { + return self.handler.ListProcesses(listType) +} + +func (self *statsSummaryContainerHandlerWrapper) StatsSummary() (*info.ContainerStatsSummary, error) { + self.lock.Lock() + defer self.lock.Unlock() + samples := make([]*info.ContainerStats, 0, self.sampler.Len()) + self.sampler.Map(func(d interface{}) { + stats := d.(*info.ContainerStats) + samples = append(samples, stats) + }) + self.currentSummary.Samples = samples + // XXX(dengnan): propabily add to StatsParameter? + self.currentSummary.FillPercentiles( + []int{50, 80, 90, 95, 99}, + []int{50, 80, 90, 95, 99}, + ) + return self.currentSummary, nil +} + +type StatsParameter struct { + Sampler string + NumSamples int + WindowSize int + ResetPeriod time.Duration +} + +func AddStatsSummary(handler ContainerHandler, parameter *StatsParameter) (ContainerHandler, error) { + sampler, err := NewSampler(parameter) + if err != nil { + return nil, err + } + return &statsSummaryContainerHandlerWrapper{ + handler: handler, + currentSummary: &info.ContainerStatsSummary{}, + sampler: sampler, + }, nil +} diff --git a/container/statssum_test.go b/container/statssum_test.go new file mode 100644 index 00000000..07adcac6 --- /dev/null +++ b/container/statssum_test.go @@ -0,0 +1,98 @@ +// Copyright 2014 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 container + +import ( + crand "crypto/rand" + "encoding/binary" + "math/rand" + "testing" + + "github.com/google/cadvisor/info" +) + +func init() { + // NOTE(dengnan): Even if we picked a good random seed, + // the random number from math/rand is still not cryptographically secure! + var seed int64 + binary.Read(crand.Reader, binary.LittleEndian, &seed) + rand.Seed(seed) +} + +type randomStatsContainer struct { + NoStatsSummary +} + +func (self *randomStatsContainer) GetSpec() (*info.ContainerSpec, error) { + return nil, nil +} + +func (self *randomStatsContainer) GetStats() (*info.ContainerStats, error) { + stats := new(info.ContainerStats) + stats.Cpu = new(info.CpuStats) + stats.Memory = new(info.MemoryStats) + stats.Memory.Usage = uint64(rand.Intn(2048)) + return stats, nil +} + +func (self *randomStatsContainer) ListContainers(listType ListType) ([]string, error) { + return nil, nil +} + +func (self *randomStatsContainer) ListThreads(listType ListType) ([]int, error) { + return nil, nil +} + +func (self *randomStatsContainer) ListProcesses(listType ListType) ([]int, error) { + return nil, nil +} + +func TestAvgMaxMemoryUsage(t *testing.T) { + handler, err := AddStatsSummary( + &randomStatsContainer{}, + &StatsParameter{ + Sampler: "uniform", + NumSamples: 10, + }, + ) + if err != nil { + t.Error(err) + } + var maxUsage uint64 + var totalUsage uint64 + N := 100 + for i := 0; i < N; i++ { + stats, err := handler.GetStats() + if err != nil { + t.Errorf("Error when get stats: %v", err) + continue + } + if stats.Memory.Usage > maxUsage { + maxUsage = stats.Memory.Usage + } + totalUsage += stats.Memory.Usage + } + summary, err := handler.StatsSummary() + if err != nil { + t.Fatalf("Error when get summary: %v", err) + } + if summary.MaxMemoryUsage != maxUsage { + t.Fatalf("Max memory usage should be %v; received %v", maxUsage, summary.MaxMemoryUsage) + } + avg := totalUsage / uint64(N) + if summary.AvgMemoryUsage != avg { + t.Fatalf("Avg memory usage should be %v; received %v", avg, summary.AvgMemoryUsage) + } +} diff --git a/container/statssumfactory.go b/container/statssumfactory.go new file mode 100644 index 00000000..5c9de935 --- /dev/null +++ b/container/statssumfactory.go @@ -0,0 +1,48 @@ +// Copyright 2014 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 container + +import "fmt" + +type statsSummaryFactory struct { + factory ContainerHandlerFactory +} + +func (self *statsSummaryFactory) String() string { + return fmt.Sprintf("%v/stats", self.factory) +} + +var globalStatsParameter StatsParameter + +func (self *statsSummaryFactory) NewContainerHandler(name string) (ContainerHandler, error) { + h, err := self.factory.NewContainerHandler(name) + if err != nil { + return nil, err + } + return AddStatsSummary(h, &globalStatsParameter) +} + +// This is a decorator for container factory. If the container handler created +// by a container factory does not implement stats summary method, then the factory +// could be decorated with this structure. +func AddStatsSummaryToFactory(factory ContainerHandlerFactory) ContainerHandlerFactory { + return &statsSummaryFactory{ + factory: factory, + } +} + +func SetStatsParameter(param *StatsParameter) { + globalStatsParameter = *param +} diff --git a/info/container.go b/info/container.go new file mode 100644 index 00000000..3f4f8e72 --- /dev/null +++ b/info/container.go @@ -0,0 +1,231 @@ +// Copyright 2014 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 info + +import ( + "sort" + "time" +) + +type CpuSpecMask struct { + Data []uint64 `json:"data,omitempty"` +} + +type CpuSpec struct { + Limit uint64 `json:"limit"` + MaxLimit uint64 `json:"max_limit"` + Mask CpuSpecMask `json:"mask,omitempty"` +} + +type MemorySpec struct { + // The amount of memory requested. Default is unlimited (-1). + // Units: bytes. + Limit uint64 `json:"limit,omitempty"` + + // The amount of guaranteed memory. Default is 0. + // Units: bytes. + Reservation uint64 `json:"reservation,omitempty"` + + // The amount of swap space requested. Default is unlimited (-1). + // Units: bytes. + SwapLimit uint64 `json:"swap_limit,omitempty"` +} + +type ContainerSpec struct { + Cpu *CpuSpec `json:"cpu,omitempty"` + Memory *MemorySpec `json:"memory,omitempty"` +} + +type ContainerInfo struct { + // The absolute name of the container. + Name string `json:"name"` + + // The direct subcontainers of the current container. + Subcontainers []string `json:"subcontainers,omitempty"` + + // The isolation used in the container. + Spec *ContainerSpec `json:"spec,omitempty"` + + // Historical statistics gathered from the container. + Stats []*ContainerStats `json:"stats,omitempty"` + + StatsSummary *ContainerStatsSummary `json:"stats_summary,omitempty"` +} + +func (self *ContainerInfo) StatsAfter(ref time.Time) []*ContainerStats { + n := len(self.Stats) + 1 + for i, s := range self.Stats { + if s.Timestamp.After(ref) { + n = i + break + } + } + if n > len(self.Stats) { + return nil + } + return self.Stats[n:] +} + +func (self *ContainerInfo) StatsStartTime() time.Time { + var ret time.Time + for _, s := range self.Stats { + if s.Timestamp.Before(ret) || ret.IsZero() { + ret = s.Timestamp + } + } + return ret +} + +func (self *ContainerInfo) StatsEndTime() time.Time { + var ret time.Time + for i := len(self.Stats) - 1; i >= 0; i-- { + s := self.Stats[i] + if s.Timestamp.After(ret) { + ret = s.Timestamp + } + } + return ret +} + +type CpuStats struct { + Usage struct { + Total uint64 `json:"total"` + PerCpu []uint64 `json:"per_cpu,omitempty"` + User uint64 `json:"user"` + System uint64 `json:"system"` + } `json:"usage"` + Load int32 `json:"load"` +} + +type MemoryStats struct { + // Memory limit, equivalent to "limit" in MemorySpec. + // Units: Bytes. + Limit uint64 `json:"limit,omitempty"` + + // Usage statistics. + + // Current memory usage, this includes all memory regardless of when it was + // accessed. + // Units: Bytes. + Usage uint64 `json:"usage,omitempty"` + + // The amount of working set memory, this includes recently accessed memory, + // dirty memory, and kernel memmory. Working set is <= "usage". + // Units: Bytes. + WorkingSet uint64 `json:"working_set,omitempty"` + + ContainerData MemoryStatsMemoryData `json:"container_data,omitempty"` + HierarchicalData MemoryStatsMemoryData `json:"hierarchical_data,omitempty"` +} + +type MemoryStatsMemoryData struct { + Pgfault uint64 `json:"pgfault,omitempty"` + Pgmajfault uint64 `json:"pgmajfault,omitempty"` +} + +type ContainerStats struct { + // The time of this stat point. + Timestamp time.Time `json:"timestamp"` + Cpu *CpuStats `json:"cpu,omitempty"` + Memory *MemoryStats `json:"memory,omitempty"` +} + +// This is not exported. +// Use FillPercentile to calculate percentiles +type percentile struct { + Percentage int `json:"percentage"` + Value uint64 `json:"value"` +} + +type ContainerStatsSummary struct { + // TODO(dengnan): More things? + MaxMemoryUsage uint64 `json:"max_memory_usage,omitempty"` + AvgMemoryUsage uint64 `json:"avg_memory_usage,omitempty"` + Samples []*ContainerStats `json:"samples,omitempty"` + MemoryUsagePercentiles []percentile `json:"memory_usage_percentiles,omitempty"` + CpuUsagePercentiles []percentile `json:"cpu_usage_percentiles,omitempty"` +} + +type uint64Slice []uint64 + +func (self uint64Slice) Len() int { + return len(self) +} + +func (self uint64Slice) Less(i, j int) bool { + return self[i] < self[j] +} + +func (self uint64Slice) Swap(i, j int) { + self[i], self[j] = self[j], self[i] +} + +func (self uint64Slice) Percentiles(ps ...int) []uint64 { + if len(self) == 0 { + return nil + } + ret := make([]uint64, 0, len(ps)) + sort.Sort(self) + for _, p := range ps { + idx := (float64(p) / 100.0) * float64(len(self)+1) + if idx > float64(len(self)-1) { + ret = append(ret, self[len(self)-1]) + } else { + ret = append(ret, self[int(idx)]) + } + } + return ret +} + +// len(bs) <= len(as) +func float64Zipuint64(as []int, bs []uint64) []percentile { + if len(bs) == 0 { + return nil + } + ret := make([]percentile, len(bs)) + for i, b := range bs { + a := as[i] + ret[i] = percentile{ + Percentage: a, + Value: b, + } + } + return ret +} + +func (self *ContainerStatsSummary) FillPercentiles(cpuPercentages, memoryPercentages []int) { + if len(self.Samples) == 0 { + return + } + cpuUsages := make([]uint64, 0, len(self.Samples)) + memUsages := make([]uint64, 0, len(self.Samples)) + + for _, sample := range self.Samples { + if sample == nil { + continue + } + if sample.Cpu != nil { + cpuUsages = append(cpuUsages, sample.Cpu.Usage.Total) + } + if sample.Memory != nil { + memUsages = append(memUsages, sample.Memory.Usage) + } + } + + cpuPercentiles := uint64Slice(cpuUsages).Percentiles(cpuPercentages...) + memPercentiles := uint64Slice(memUsages).Percentiles(memoryPercentages...) + self.CpuUsagePercentiles = float64Zipuint64(cpuPercentages, cpuPercentiles) + self.MemoryUsagePercentiles = float64Zipuint64(memoryPercentages, memPercentiles) +} diff --git a/info/container_test.go b/info/container_test.go new file mode 100644 index 00000000..3c0d7342 --- /dev/null +++ b/info/container_test.go @@ -0,0 +1,86 @@ +// Copyright 2014 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 info + +import ( + "testing" + "time" +) + +func TestStatsStartTime(t *testing.T) { + N := 10 + stats := make([]*ContainerStats, 0, N) + ct := time.Now() + for i := 0; i < N; i++ { + s := &ContainerStats{ + Timestamp: ct.Add(time.Duration(i) * time.Second), + } + stats = append(stats, s) + } + cinfo := &ContainerInfo{ + Name: "/some/container", + Stats: stats, + } + ref := ct.Add(time.Duration(N-1) * time.Second) + end := cinfo.StatsEndTime() + + if !ref.Equal(end) { + t.Errorf("end time is %v; should be %v", end, ref) + } +} + +func TestStatsEndTime(t *testing.T) { + N := 10 + stats := make([]*ContainerStats, 0, N) + ct := time.Now() + for i := 0; i < N; i++ { + s := &ContainerStats{ + Timestamp: ct.Add(time.Duration(i) * time.Second), + } + stats = append(stats, s) + } + cinfo := &ContainerInfo{ + Name: "/some/container", + Stats: stats, + } + ref := ct + start := cinfo.StatsStartTime() + + if !ref.Equal(start) { + t.Errorf("start time is %v; should be %v", start, ref) + } +} + +func TestPercentiles(t *testing.T) { + N := 100 + data := make([]uint64, N) + + for i := 0; i < N; i++ { + data[i] = uint64(i) + } + ps := []float64{ + 0.8, + 0.9, + 0.5, + } + ss := uint64Slice(data).Percentiles(ps...) + for i, s := range ss { + p := ps[i] + d := uint64(float64(N) * p) + if d != s { + t.Errorf("%v \\%tile data should be %v, but got %v", p*float64(100), d, s) + } + } +} diff --git a/info/machine.go b/info/machine.go new file mode 100644 index 00000000..7d0f68c2 --- /dev/null +++ b/info/machine.go @@ -0,0 +1,27 @@ +// Copyright 2014 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 info + +type MachineInfo struct { + // The number of cores in this machine. + NumCores int `json:"num_cores"` + + // The amount of memory (in bytes) in this machine + MemoryCapacity int64 `json:"memory_capacity"` +} + +type MachineInfoFactory interface { + GetMachineInfo() (*MachineInfo, error) +} diff --git a/manager/container.go b/manager/container.go new file mode 100644 index 00000000..b167b049 --- /dev/null +++ b/manager/container.go @@ -0,0 +1,176 @@ +// Copyright 2014 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. + +// Per-container manager. + +package manager + +import ( + "container/list" + "flag" + "log" + "sync" + "time" + + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/info" +) + +var historyDuration = flag.Int("history_duration", 60, "number of seconds of container history to keep") + +// Internal mirror of the external data structure. +type containerStat struct { + Timestamp time.Time + Data *info.ContainerStats +} +type containerInfo struct { + Name string + Subcontainers []string + Spec *info.ContainerSpec + Stats *list.List + StatsSummary *info.ContainerStatsSummary +} + +type containerData struct { + handler container.ContainerHandler + info containerInfo + lock sync.Mutex + + // Tells the container to stop. + stop chan bool +} + +func (c *containerData) Start() error { + // Force the first update. + c.housekeepingTick() + log.Printf("Start housekeeping for container %q\n", c.info.Name) + + go c.housekeeping() + return nil +} + +func (c *containerData) Stop() error { + c.stop <- true + return nil +} + +func (c *containerData) GetInfo() (*containerInfo, error) { + // TODO(vmarmol): Consider caching this. + // Get spec and subcontainers. + err := c.updateSpec() + if err != nil { + return nil, err + } + err = c.updateSubcontainers() + if err != nil { + return nil, err + } + + // Make a copy of the info for the user. + c.lock.Lock() + defer c.lock.Unlock() + ret := c.info + return &ret, nil +} + +func NewContainerData(containerName string) (*containerData, error) { + cont := &containerData{} + handler, err := container.NewContainerHandler(containerName) + if err != nil { + return nil, err + } + cont.handler = handler + cont.info.Name = containerName + cont.info.Stats = list.New() + cont.stop = make(chan bool, 1) + + return cont, nil +} + +func (c *containerData) housekeeping() { + // Housekeep every second. + for true { + select { + case <-c.stop: + // Stop housekeeping when signaled. + return + case <-time.Tick(time.Second): + start := time.Now() + c.housekeepingTick() + + // Log if housekeeping took longer than 120ms. + duration := time.Since(start) + if duration >= 120*time.Millisecond { + log.Printf("Housekeeping(%s) took %s", c.info.Name, duration) + } + } + } +} + +func (c *containerData) housekeepingTick() { + err := c.updateStats() + if err != nil { + log.Printf("Failed to update stats for container \"%s\": %s", c.info.Name, err) + } +} + +func (c *containerData) updateSpec() error { + spec, err := c.handler.GetSpec() + if err != nil { + return err + } + c.lock.Lock() + defer c.lock.Unlock() + c.info.Spec = spec + return nil +} + +func (c *containerData) updateStats() error { + stats, err := c.handler.GetStats() + if err != nil { + return err + } + if stats == nil { + return nil + } + summary, err := c.handler.StatsSummary() + if err != nil { + return err + } + timestamp := time.Now() + + // Remove the front if we go over. + c.lock.Lock() + defer c.lock.Unlock() + if c.info.Stats.Len() >= *historyDuration { + c.info.Stats.Remove(c.info.Stats.Front()) + } + c.info.Stats.PushBack(&containerStat{ + Timestamp: timestamp, + Data: stats, + }) + c.info.StatsSummary = summary + return nil +} + +func (c *containerData) updateSubcontainers() error { + subcontainers, err := c.handler.ListContainers(container.LIST_SELF) + if err != nil { + return err + } + c.lock.Lock() + defer c.lock.Unlock() + c.info.Subcontainers = subcontainers + return nil +} diff --git a/manager/machine.go b/manager/machine.go new file mode 100644 index 00000000..8ec13d1b --- /dev/null +++ b/manager/machine.go @@ -0,0 +1,61 @@ +// Copyright 2014 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 manager + +import ( + "fmt" + "io/ioutil" + "regexp" + "strconv" + + "github.com/google/cadvisor/info" +) + +var numCpuRegexp = regexp.MustCompile("processor\\t*: +[0-9]+") +var memoryCapacityRegexp = regexp.MustCompile("MemTotal: *([0-9]+) kB") + +func getMachineInfo() (*info.MachineInfo, error) { + // Get the number of CPUs from /proc/cpuinfo. + out, err := ioutil.ReadFile("/proc/cpuinfo") + if err != nil { + return nil, err + } + numCores := len(numCpuRegexp.FindAll(out, -1)) + if numCores == 0 { + return nil, fmt.Errorf("failed to count cores in output: %s", string(out)) + } + + // Get the amount of usable memory from /proc/meminfo. + out, err = ioutil.ReadFile("/proc/meminfo") + if err != nil { + return nil, err + } + matches := memoryCapacityRegexp.FindSubmatch(out) + if len(matches) != 2 { + return nil, fmt.Errorf("failed to find memory capacity in output: %s", string(out)) + } + memoryCapacity, err := strconv.ParseInt(string(matches[1]), 10, 64) + if err != nil { + return nil, err + } + + // Capacity is in KB, convert it to bytes. + memoryCapacity = memoryCapacity * 1024 + + return &info.MachineInfo{ + NumCores: numCores, + MemoryCapacity: memoryCapacity, + }, nil +} diff --git a/manager/manager.go b/manager/manager.go new file mode 100644 index 00000000..6320fa19 --- /dev/null +++ b/manager/manager.go @@ -0,0 +1,247 @@ +// Copyright 2014 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 manager + +import ( + "fmt" + "log" + "sync" + "time" + + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/info" +) + +type Manager interface { + // Start the manager, blocks forever. + Start() error + + // Get information about a container. + GetContainerInfo(containerName string) (*info.ContainerInfo, error) + + // Get information about the machine. + GetMachineInfo() (*info.MachineInfo, error) +} + +func New() (Manager, error) { + newManager := &manager{} + newManager.containers = make(map[string]*containerData) + + machineInfo, err := getMachineInfo() + if err != nil { + return nil, err + } + newManager.machineInfo = *machineInfo + log.Printf("Machine: %+v", newManager.machineInfo) + return newManager, nil +} + +type manager struct { + containers map[string]*containerData + containersLock sync.RWMutex + machineInfo info.MachineInfo +} + +// Start the container manager. +func (m *manager) Start() error { + // Create root and then recover all containers. + _, err := m.createContainer("/") + if err != nil { + return err + } + log.Printf("Starting recovery of all containers") + err = m.detectContainers() + if err != nil { + return err + } + log.Printf("Recovery completed") + + // Look for new containers in the main housekeeping thread. + for t := range time.Tick(time.Second) { + start := time.Now() + + // Check for new containers. + err = m.detectContainers() + if err != nil { + log.Printf("Failed to detect containers: %s", err) + } + + // Log if housekeeping took more than 100ms. + duration := time.Since(start) + if duration >= 100*time.Millisecond { + log.Printf("Global Housekeeping(%d) took %s", t.Unix(), duration) + } + } + return nil +} + +// Get a container by name. +func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, error) { + log.Printf("Get(%s)", containerName) + var cont *containerData + var ok bool + func() { + m.containersLock.RLock() + defer m.containersLock.RUnlock() + + // Ensure we have the container. + cont, ok = m.containers[containerName] + }() + if !ok { + return nil, fmt.Errorf("unknown container \"%s\"", containerName) + } + + // Get the info from the container. + cinfo, err := cont.GetInfo() + if err != nil { + return nil, err + } + + // Make a copy of the info for the user. + ret := &info.ContainerInfo{ + Name: cinfo.Name, + Subcontainers: cinfo.Subcontainers, + Spec: cinfo.Spec, + StatsSummary: cinfo.StatsSummary, + } + + // Set default value to an actual value + if ret.Spec.Memory != nil { + // Memory.Limit is 0 means there's no limit + if ret.Spec.Memory.Limit == 0 { + ret.Spec.Memory.Limit = uint64(m.machineInfo.MemoryCapacity) + } + } + ret.Stats = make([]*info.ContainerStats, 0, cinfo.Stats.Len()) + for e := cinfo.Stats.Front(); e != nil; e = e.Next() { + data := e.Value.(*containerStat) + ret.Stats = append(ret.Stats, data.Data) + } + return ret, nil +} + +func (m *manager) GetMachineInfo() (*info.MachineInfo, error) { + // Copy and return the MachineInfo. + ret := m.machineInfo + return &ret, nil +} + +// Create a container. This expects to only be called from the global manager thread. +func (m *manager) createContainer(containerName string) (*containerData, error) { + cont, err := NewContainerData(containerName) + if err != nil { + return nil, err + } + + // Add to the containers map. + func() { + m.containersLock.Lock() + defer m.containersLock.Unlock() + + log.Printf("Added container: %s", containerName) + m.containers[containerName] = cont + }() + + // Start the container's housekeeping. + cont.Start() + return cont, nil +} + +func (m *manager) destroyContainer(containerName string) error { + m.containersLock.Lock() + defer m.containersLock.Unlock() + + cont, ok := m.containers[containerName] + if !ok { + return fmt.Errorf("Expected container \"%s\" to exist during destroy", containerName) + } + + // Tell the container to stop. + err := cont.Stop() + if err != nil { + return err + } + + // Remove the container from our records. + delete(m.containers, containerName) + log.Printf("Destroyed container: %s", containerName) + return nil +} + +type empty struct{} + +// Detect all containers that have been added or deleted. +func (m *manager) getContainersDiff() (added []string, removed []string, err error) { + // TODO(vmarmol): We probably don't need to lock around / since it will always be there. + m.containersLock.RLock() + defer m.containersLock.RUnlock() + + // Get all containers on the system. + cont, ok := m.containers["/"] + if !ok { + return nil, nil, fmt.Errorf("Failed to find container \"/\" while checking for new containers") + } + allContainers, err := cont.handler.ListContainers(container.LIST_RECURSIVE) + if err != nil { + return nil, nil, err + } + allContainers = append(allContainers, "/") + + // Determine which were added and which were removed. + allContainersSet := make(map[string]*empty) + for name, _ := range m.containers { + allContainersSet[name] = &empty{} + } + for _, name := range allContainers { + delete(allContainersSet, name) + _, ok := m.containers[name] + if !ok { + added = append(added, name) + } + } + + // Removed ones are no longer in the container listing. + for name, _ := range allContainersSet { + removed = append(removed, name) + } + + return +} + +// Detect the existing containers and reflect the setup here. +func (m *manager) detectContainers() error { + added, removed, err := m.getContainersDiff() + if err != nil { + return err + } + + // Add the new containers. + for _, name := range added { + _, err = m.createContainer(name) + if err != nil { + return fmt.Errorf("Failed to create existing container: %s: %s", name, err) + } + } + + // Remove the old containers. + for _, name := range removed { + err = m.destroyContainer(name) + if err != nil { + return fmt.Errorf("Failed to destroy existing container: %s: %s", name, err) + } + } + + return nil +} diff --git a/pages/containers.go b/pages/containers.go new file mode 100644 index 00000000..8efb84d2 --- /dev/null +++ b/pages/containers.go @@ -0,0 +1,199 @@ +// Copyright 2014 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. + +// Page for /containers/ +package pages + +import ( + "fmt" + "html/template" + "log" + "net/http" + "net/url" + "path" + "strconv" + "strings" + "time" + + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/manager" +) + +const ContainersPage = "/containers/" + +var funcMap = template.FuncMap{ + "containerLink": containerLink, + "printMask": printMask, + "printCores": printCores, + "printMegabytes": printMegabytes, + "containerNameEquals": containerNameEquals, + "getMemoryUsage": getMemoryUsage, + "getMemoryUsagePercent": getMemoryUsagePercent, + "getHotMemoryPercent": getHotMemoryPercent, + "getColdMemoryPercent": getColdMemoryPercent, +} + +// TODO(vmarmol): Consider housekeeping Spec too so we can show changes through time. We probably don't need it ever second though. + +var pageTemplate *template.Template + +type pageData struct { + ContainerName string + ParentContainers []string + Subcontainers []string + Spec *info.ContainerSpec + Stats []*info.ContainerStats + MachineInfo *info.MachineInfo + ResourcesAvailable bool + CpuAvailable bool + MemoryAvailable bool +} + +func init() { + pageTemplate = template.New("containersTemplate").Funcs(funcMap) + _, err := pageTemplate.Parse(containersHtmlTemplate) + if err != nil { + log.Fatalf("Failed to parse template: %s", err) + } +} + +// TODO(vmarmol): Escape this correctly. +func containerLink(containerName string, basenameOnly bool, cssClasses string) interface{} { + var displayName string + if basenameOnly { + displayName = path.Base(string(containerName)) + } else { + displayName = string(containerName) + } + if containerName == "root" { + containerName = "/" + } + return template.HTML(fmt.Sprintf("%s", cssClasses, ContainersPage[:len(ContainersPage)-1], containerName, displayName)) +} + +func containerNameEquals(c1 string, c2 string) bool { + return c1 == c2 +} + +func printMask(mask *info.CpuSpecMask, numCores int) interface{} { + // TODO(vmarmol): Detect this correctly. + // TODO(vmarmol): Support more than 64 cores. + rawMask := uint64(0) + if len(mask.Data) > 0 { + rawMask = mask.Data[0] + } + masks := make([]string, numCores) + for i := uint(0); i < uint(numCores); i++ { + coreClass := "inactive-cpu" + // by default, all cores are active + if ((0x1<%d", coreClass, i) + } + return template.HTML(strings.Join(masks, " ")) +} + +func printCores(millicores *uint64) string { + // TODO(vmarmol): Detect this correctly + if *millicores > 1024*1000 { + return "unlimited" + } + cores := float64(*millicores) / 1000 + return strconv.FormatFloat(cores, 'f', 3, 64) +} + +func toMegabytes(bytes uint64) float64 { + return float64(bytes) / (1 << 20) +} + +func printMegabytes(bytes uint64) string { + // TODO(vmarmol): Detect this correctly + if bytes > (100 << 30) { + return "unlimited" + } + megabytes := toMegabytes(bytes) + return strconv.FormatFloat(megabytes, 'f', 3, 64) +} + +func toMemoryPercent(usage uint64, spec *info.ContainerSpec) int { + return int((usage * 100) / (spec.Memory.Limit)) +} + +func getMemoryUsage(stats []*info.ContainerStats) string { + return strconv.FormatFloat(toMegabytes((stats[len(stats)-1].Memory.Usage)), 'f', 2, 64) +} + +func getMemoryUsagePercent(spec *info.ContainerSpec, stats []*info.ContainerStats) int { + return toMemoryPercent((stats[len(stats)-1].Memory.Usage), spec) +} + +func getHotMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats) int { + return toMemoryPercent((stats[len(stats)-1].Memory.WorkingSet), spec) +} + +func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats) int { + latestStats := stats[len(stats)-1].Memory + return toMemoryPercent((latestStats.Usage)-(latestStats.WorkingSet), spec) +} + +func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error { + start := time.Now() + + // The container name is the path after the handler + containerName := u.Path[len(ContainersPage)-1:] + + // Get the container. + cont, err := m.GetContainerInfo(containerName) + if err != nil { + return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err) + } + + // Get the MachineInfo + machineInfo, err := m.GetMachineInfo() + if err != nil { + return err + } + + // Make a list of the parent containers and their links + var parentContainers []string + parentContainers = append(parentContainers, string("root")) + parentName := "" + for _, part := range strings.Split(string(cont.Name), "/") { + if part == "" { + continue + } + parentName += "/" + part + parentContainers = append(parentContainers, string(parentName)) + } + + data := &pageData{ + ContainerName: cont.Name, + ParentContainers: parentContainers, + Subcontainers: cont.Subcontainers, + Spec: cont.Spec, + Stats: cont.Stats, + MachineInfo: machineInfo, + ResourcesAvailable: cont.Spec.Cpu != nil || cont.Spec.Memory != nil, + CpuAvailable: cont.Spec.Cpu != nil, + MemoryAvailable: cont.Spec.Memory != nil, + } + err = pageTemplate.Execute(w, data) + if err != nil { + log.Printf("Failed to apply template: %s", err) + } + + log.Printf("Request took %s", time.Since(start)) + return nil +} diff --git a/pages/containers_html.go b/pages/containers_html.go new file mode 100644 index 00000000..7e140e50 --- /dev/null +++ b/pages/containers_html.go @@ -0,0 +1,160 @@ +// Copyright 2014 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 pages + +const containersHtmlTemplate = ` + + + cAdvisor - Container {{.ContainerName}} + + + + + + + + + + + + + + + + +
+ +
+ + +
+ {{if .Subcontainers}} +
+ +
+ {{range $subcontainer := .Subcontainers}} + {{containerLink $subcontainer false "list-group-item"}} + {{end}} +
+
+ {{end}} + {{if .ResourcesAvailable}} +
+ + {{if .CpuAvailable}} +
    +
  • CPU
  • + {{if .Spec.Cpu.Limit}} +
  • Limit {{printCores .Spec.Cpu.Limit}} cores
  • + {{end}} + {{if .Spec.Cpu.MaxLimit}} +
  • Max Limit {{printCores .Spec.Cpu.MaxLimit}} cores
  • + {{end}} + {{if .Spec.Cpu.Mask}} +
  • Allowed Cores {{printMask .Spec.Cpu.Mask .MachineInfo.NumCores}}
  • + {{end}} +
+ {{end}} + {{if .MemoryAvailable}} +
    +
  • Memory
  • + {{if .Spec.Memory.Reservation}} +
  • Reservation {{printMegabytes .Spec.Memory.Reservation}} MB
  • + {{end}} + {{if .Spec.Memory.Limit}} +
  • Limit {{printMegabytes .Spec.Memory.Limit}} MB
  • + {{end}} + {{if .Spec.Memory.SwapLimit}} +
  • Swap Limit {{printMegabytes .Spec.Memory.SwapLimit}} MB
  • + {{end}} +
+ {{end}} +
+
+ +
+
+

Overview

+
+
+
+
+ {{if .CpuAvailable}} +
+
+

CPU

+
+
+

Total Usage

+
+

Usage per Core

+
+

Usage Breakdown

+
+
+
+ {{end}} + {{if .MemoryAvailable}} +
+
+

Memory

+
+
+

Total Usage

+
+
+
+

Usage Breakdown

+
+
+
+ Hot Memory +
+
+ Cold Memory +
+
+
+
+ {{ getMemoryUsage .Stats }} MB ({{ getMemoryUsagePercent .Spec .Stats }}%) +
+
+

Page Faults

+
+
+
+ {{end}} +
+ {{end}} +
+ + + +` diff --git a/pages/static/containers_css.go b/pages/static/containers_css.go new file mode 100644 index 00000000..dec45c54 --- /dev/null +++ b/pages/static/containers_css.go @@ -0,0 +1,47 @@ +// Copyright 2014 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 static + +const containersCss = ` +.stat-label { + font-weight:bold; +} +.unit-label { + color:#888888; + font-style:italic; +} +.active-cpu { + font-weight:bold; + color:#000000; +} +.inactive-cpu { + color:#888888; +} +.raw-stats { + font-family: "Courier New"; + white-space: pre-wrap; +} +.isolation-title { + color:#FFFFFF; +} +#logo { + height: 200px; + margin-top: 20px; + background-repeat: no-repeat; + background-size: contain; + background-position: center; + background-image:url("data:image/png;base64,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AAAAAOQRxscBAAAAAAAAAAAAQB5hfBwAAAAAAAAAAAAA5BHGxwEAAAAAAAAAAABAHmF8HAAAAAAAAAAAAADkEcbHAQAAAAAAAAAAAEAeYXwcAAAAAAAAAAAAAOQRxscBAAAAAAAAAAAAQB5hfBwAAAAAAAAAAAAA5BHGxwEAAAAAAAAAAABAHmF8HAAAAAAAAAAAAADkEcbHAQAAAAAAAAAAAEAeYXwcAAAAAAAAAAAAAOQRxscBAAAAAAAAAAAAQB5hfBwAAAAAAAAAAAAA5BHGxwEAAAAAAAAAAABAHmF8HAAAAAAAAAAAAADkEcbHAQAAAAAAAAAAAEAeYXwcAAAAAAAAAAAAAOQRxscBAAAAAAAAAAAAQB5hfBwAAAAAAAAAAAAA5BHGxwEAAAAAAAAAAABAHmF8HAAAAAAAAAAAAADkEcbHAQAAAAAAAAAAAEAeYXwcAAAAAAAAAAAAAOQRxscBAAAAAAAAAAAAQB5hfBwAAAAAAAAAAAAA5BHGxwEAAAAAAAAAAABAHmF8HAAAAAAAAAAAAADkEcbHAQAAAAAAAAAAAEAeYXwcAAAAAAAAAAAAAOQRxscBAAAAAAAAAAAAQB5hfBwAAAAAAAAAAAAA5BHGxwEAAAAAAAAAAABAHv0fr+hlXOtHm2kAAAAASUVORK5CYII="); +} +` diff --git a/pages/static/containers_js.go b/pages/static/containers_js.go new file mode 100644 index 00000000..83e1d028 --- /dev/null +++ b/pages/static/containers_js.go @@ -0,0 +1,288 @@ +// Copyright 2014 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 static + +const containersJs = ` +google.load("visualization", "1", {packages: ["corechart", "gauge"]}); + +// Draw a line chart. +function drawLineChart(seriesTitles, data, elementId, unit) { + // Convert the first column to a Date. + for (var i = 0; i < data.length; i++) { + if (data[i] != null) { + data[i][0] = new Date(data[i][0]); + } + } + + // Add the definition of each column and the necessary data. + var dataTable = new google.visualization.DataTable(); + dataTable.addColumn('datetime', seriesTitles[0]); + for (var i = 1; i < seriesTitles.length; i++) { + dataTable.addColumn('number', seriesTitles[i]); + } + dataTable.addRows(data); + + // Create and draw the visualization. + var ac = null; + var opts = null; + // TODO(vmarmol): Remove this hack, it is to support the old charts and the new charts during the transition. + if (window.charts) { + if (!(elementId in window.charts)) { + ac = new google.visualization.LineChart(document.getElementById(elementId)); + window.charts[elementId] = ac; + } + ac = window.charts[elementId]; + opts = window.chartOptions; + } else { + ac = new google.visualization.LineChart(document.getElementById(elementId)); + opts = {}; + } + opts.vAxis = {title: unit}; + ac.draw(dataTable, window.chartOptions); +} + +// Draw a gauge. +function drawGauge(elementId, cpuUsage, memoryUsage) { + var gauges = [['Label', 'Value']]; + if (cpuUsage >= 0) { + gauges.push(['CPU', cpuUsage]); + } + if (memoryUsage >= 0) { + gauges.push(['Memory', memoryUsage]); + } + // Create and populate the data table. + var data = google.visualization.arrayToDataTable(gauges); + + // Create and draw the visualization. + var options = { + width: 400, height: 120, + redFrom: 90, redTo: 100, + yellowFrom:75, yellowTo: 90, + minorTicks: 5, + animation: { + duration: 900, + easing: 'linear' + } + }; + var chart = new google.visualization.Gauge(document.getElementById(elementId)); + chart.draw(data, options); +} + +// Get the machine info. +function getMachineInfo(callback) { + $.getJSON("/api/v1.0/machine", function(data) { + callback(data); + }); +} + +// Get the container stats for the specified container. +function getStats(containerName, callback) { + $.getJSON("/api/v1.0/containers" + containerName, function(data) { + callback(data); + }); +} + +// Draw the graph for CPU usage. +function drawCpuTotalUsage(elementId, machineInfo, stats) { + var titles = ["Time", "Total"]; + var data = []; + for (var i = 1; i < stats.stats.length; i++) { + var cur = stats.stats[i]; + var prev = stats.stats[i - 1]; + + // TODO(vmarmol): This assumes we sample every second, use the timestamps. + var elements = []; + elements.push(cur.timestamp); + elements.push((cur.cpu.usage.total - prev.cpu.usage.total) / 1000000000); + data.push(elements); + } + drawLineChart(titles, data, elementId, "Cores"); +} + +// Draw the graph for per-core CPU usage. +function drawCpuPerCoreUsage(elementId, machineInfo, stats) { + // Add a title for each core. + var titles = ["Time"]; + for (var i = 0; i < machineInfo.num_cores; i++) { + titles.push("Core " + i); + } + var data = []; + for (var i = 1; i < stats.stats.length; i++) { + var cur = stats.stats[i]; + var prev = stats.stats[i - 1]; + + var elements = []; + elements.push(cur.timestamp); + for (var j = 0; j < machineInfo.num_cores; j++) { + // TODO(vmarmol): This assumes we sample every second, use the timestamps. + elements.push((cur.cpu.usage.per_cpu[j] - prev.cpu.usage.per_cpu[j]) / 1000000000); + } + data.push(elements); + } + drawLineChart(titles, data, elementId, "Cores"); +} + +// Draw the graph for CPU usage breakdown. +function drawCpuUsageBreakdown(elementId, containerInfo) { + var titles = ["Time", "User", "Kernel"]; + var data = []; + for (var i = 1; i < containerInfo.stats.length; i++) { + var cur = containerInfo.stats[i]; + var prev = containerInfo.stats[i - 1]; + + // TODO(vmarmol): This assumes we sample every second, use the timestamps. + var elements = []; + elements.push(cur.timestamp); + elements.push((cur.cpu.usage.user - prev.cpu.usage.user) / 1000000000); + elements.push((cur.cpu.usage.system - prev.cpu.usage.system) / 1000000000); + data.push(elements); + } + drawLineChart(titles, data, elementId, "Cores"); +} + +// Draw the gauges for overall resource usage. +function drawOverallUsage(elementId, containerInfo) { + var cur = containerInfo.stats[containerInfo.stats.length - 1]; + + var cpuUsage = 0; + if (containerInfo.spec.cpu && containerInfo.stats.length >= 2) { + var prev = containerInfo.stats[containerInfo.stats.length - 2]; + var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total; + + // Convert to millicores and take the percentage + cpuUsage = Math.round(((rawUsage / 1000000) / containerInfo.spec.cpu.limit) * 100); + if (cpuUsage > 100) { + cpuUsage = 100; + } + } + + var memoryUsage = 0; + if (containerInfo.spec.memory) { + memoryUsage = Math.round((cur.memory.usage / containerInfo.spec.memory.limit) * 100); + } + + drawGauge(elementId, cpuUsage, memoryUsage); +} + +var oneMegabyte = 1024 * 1024; + +function drawMemoryUsage(elementId, containerInfo) { + var titles = ["Time", "Total"]; + var data = []; + for (var i = 0; i < containerInfo.stats.length; i++) { + var cur = containerInfo.stats[i]; + + // TODO(vmarmol): This assumes we sample every second, use the timestamps. + var elements = []; + elements.push(cur.timestamp); + elements.push(cur.memory.usage / oneMegabyte); + data.push(elements); + } + drawLineChart(titles, data, elementId, "Megabytes"); +} + +function drawMemoryPageFaults(elementId, containerInfo) { + var titles = ["Time", "Faults", "Major Faults"]; + var data = []; + for (var i = 1; i < containerInfo.stats.length; i++) { + var cur = containerInfo.stats[i]; + var prev = containerInfo.stats[i - 1]; + + // TODO(vmarmol): This assumes we sample every second, use the timestamps. + var elements = []; + elements.push(cur.timestamp); + elements.push(cur.memory.hierarchical_data.pgfault - prev.memory.hierarchical_data.pgfault); + // TODO(vmarmol): Fix to expose this data. + //elements.push(cur.memory.hierarchical_data.pgmajfault - prev.memory.hierarchical_data.pgmajfault); + elements.push(0); + data.push(elements); + } + drawLineChart(titles, data, elementId, "Faults"); +} + +// Expects an array of closures to call. After each execution the JS runtime is given control back before continuing. +// This function returns asynchronously +function stepExecute(steps) { + // No steps, stop. + if (steps.length == 0) { + return; + } + + // Get a step and execute it. + var step = steps.shift(); + step(); + + // Schedule the next step. + setTimeout(function() { + stepExecute(steps); + }, 0); +} + +// Draw all the charts on the page. +function drawCharts(machineInfo, containerInfo) { + var steps = []; + + steps.push(function() { + drawOverallUsage("usage-gauge", containerInfo) + }); + + // CPU. + steps.push(function() { + drawCpuTotalUsage("cpu-total-usage-chart", machineInfo, containerInfo); + }); + steps.push(function() { + drawCpuPerCoreUsage("cpu-per-core-usage-chart", machineInfo, containerInfo); + }); + steps.push(function() { + drawCpuUsageBreakdown("cpu-usage-breakdown-chart", containerInfo); + }); + + // Memory. + steps.push(function() { + drawMemoryUsage("memory-usage-chart", containerInfo); + }); + steps.push(function() { + drawMemoryPageFaults("memory-page-faults-chart", containerInfo); + }); + + stepExecute(steps); +} + +// Executed when the page finishes loading. +function startPage(containerName, hasCpu, hasMemory) { + // Don't fetch data if we don't have any resource. + if (!hasCpu && !hasMemory) { + return; + } + + // TODO(vmarmol): Look into changing the view window to get a smoother animation. + window.chartOptions = { + curveType: 'function', + height: 300, + legend:{position:"none"}, + focusTarget: "category", + }; + window.charts = {}; + + // Get machine info, then get the stats every 1s. + getMachineInfo(function(machineInfo) { + setInterval(function() { + getStats(containerName, function(stats){ + drawCharts(machineInfo, stats); + }); + }, 1000); + }); +} +` diff --git a/pages/static/static.go b/pages/static/static.go new file mode 100644 index 00000000..4d3551ff --- /dev/null +++ b/pages/static/static.go @@ -0,0 +1,46 @@ +// Copyright 2014 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. + +// Handler for /static content. + +package static + +import ( + "fmt" + "net/http" + "net/url" +) + +const StaticResource = "/static/" + +var staticFiles = map[string]string{ + "containers.css": containersCss, + "containers.js": containersJs, +} + +func HandleRequest(w http.ResponseWriter, u *url.URL) error { + if len(u.Path) <= len(StaticResource) { + return fmt.Errorf("unknown static resource %q", u.Path) + } + + // Get the static content if it exists. + resource := u.Path[len(StaticResource):] + content, ok := staticFiles[resource] + if !ok { + return fmt.Errorf("unknown static resource %q", resource) + } + + _, err := w.Write([]byte(content)) + return err +} diff --git a/quickstart/Dockerfile b/quickstart/Dockerfile new file mode 100644 index 00000000..1df0fff6 --- /dev/null +++ b/quickstart/Dockerfile @@ -0,0 +1,14 @@ +FROM ubuntu +MAINTAINER kyurtsever@google.com dengnan@google.com vmarmol@google.com + +# Get the lmctfy dependencies. +RUN apt-get update && apt-get upgrade -y +RUN apt-get install -y --force-yes pkg-config libprotobuf8 libapparmor1 +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0 + +# Get the lcmtfy and cAdvisor binaries. +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/lmctfy /usr/bin/lmctfy +ADD http://storage.googleapis.com/cadvisor-bin/cadvisor /usr/bin/cadvisor +RUN chmod +x /usr/bin/lmctfy && chmod +x /usr/bin/cadvisor + +EXPOSE 8080 diff --git a/sampling/autofilter.go b/sampling/autofilter.go new file mode 100644 index 00000000..7389de52 --- /dev/null +++ b/sampling/autofilter.go @@ -0,0 +1,51 @@ +// Copyright 2014 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 sampling + +type autoFilterSampler struct { + // filter will run to remove elements before adding every observation + filter func(d interface{}) bool + sampler Sampler +} + +func (self *autoFilterSampler) Len() int { + return self.sampler.Len() +} + +func (self *autoFilterSampler) Reset() { + self.sampler.Reset() +} + +func (self *autoFilterSampler) Map(f func(d interface{})) { + self.sampler.Map(f) +} + +func (self *autoFilterSampler) Filter(filter func(d interface{}) bool) { + self.sampler.Filter(filter) +} + +func (self *autoFilterSampler) Update(d interface{}) { + self.Filter(self.filter) + self.sampler.Update(d) +} + +// Add a decorator for sampler. Whenever an Update() is called, the sampler will +// call filter() first to remove elements in the decorated sampler. +func NewAutoFilterSampler(sampler Sampler, filter func(d interface{}) bool) Sampler { + return &autoFilterSampler{ + filter: filter, + sampler: sampler, + } +} diff --git a/sampling/autoreset.go b/sampling/autoreset.go new file mode 100644 index 00000000..0b7da0ca --- /dev/null +++ b/sampling/autoreset.go @@ -0,0 +1,60 @@ +// Copyright 2014 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 sampling + +import "time" + +type autoResetSampler struct { + shouldReset func(d interface{}) bool + sampler Sampler +} + +func (self *autoResetSampler) Len() int { + return self.sampler.Len() +} + +func (self *autoResetSampler) Reset() { + self.sampler.Reset() +} + +func (self *autoResetSampler) Map(f func(d interface{})) { + self.sampler.Map(f) +} + +func (self *autoResetSampler) Filter(filter func(d interface{}) bool) { + self.sampler.Filter(filter) +} + +func (self *autoResetSampler) Update(d interface{}) { + if self.shouldReset(d) { + self.sampler.Reset() + } + self.sampler.Update(d) +} + +func NewPeriodcallyResetSampler(period time.Duration, sampler Sampler) Sampler { + lastRest := time.Now() + shouldReset := func(d interface{}) bool { + if time.Now().Sub(lastRest) > period { + lastRest = time.Now() + return true + } + return false + } + return &autoResetSampler{ + shouldReset: shouldReset, + sampler: sampler, + } +} diff --git a/sampling/chainsample.go b/sampling/chainsample.go new file mode 100644 index 00000000..8154e2e5 --- /dev/null +++ b/sampling/chainsample.go @@ -0,0 +1,157 @@ +package sampling + +import ( + "log" + "math/rand" + "sync" + + "github.com/kr/pretty" +) + +type empty struct{} + +// Randomly generate number [start,end) except @except. +func randInt64Except(start, end int64, except map[int64]empty) int64 { + n := end - start + ret := rand.Int63n(n) + start + for _, ok := except[ret]; ok; _, ok = except[ret] { + ret = rand.Int63n(n) + start + } + return ret +} + +// Basic idea: +// Every obervation will have a sequence number as its id. +// Suppose we want to sample k observations within latest n observations +// At first, we generated k random numbers in [0,n). These random numbers +// will be used as ids of observations that will be sampled. +type chainSampler struct { + sampleSize int + windowSize int64 + + // Every observation will have a sequence number starting from 1. + // The sequence number must increase by one for each observation. + numObservations int64 + + // All samples stored as id -> value. + samples map[int64]interface{} + + // The set of id of future observations. + futureSamples map[int64]empty + + // The chain of samples: old observation id -> future observation id. + // When the old observation expires, the future observation will be + // stored as a sample. + sampleChain map[int64]int64 + + // Replacements are: observations whose previous sample is not expired + // id->value. + replacements map[int64]interface{} + lock sync.RWMutex +} + +func (self *chainSampler) initFutureSamples() { + for i := 0; i < self.sampleSize; i++ { + n := randInt64Except(1, self.windowSize+1, self.futureSamples) + self.futureSamples[n] = empty{} + } +} + +func (self *chainSampler) arrive(seqNum int64, obv interface{}) { + if _, ok := self.futureSamples[seqNum]; !ok { + // If this observation is not selected, ignore it. + return + } + + delete(self.futureSamples, seqNum) + + if len(self.samples) < self.sampleSize { + self.samples[seqNum] = obv + } + self.replacements[seqNum] = obv + + // Select a future observation which will replace current observation + // when it expires. + futureSeqNum := randInt64Except(seqNum+1, seqNum+self.windowSize+1, self.futureSamples) + self.futureSamples[futureSeqNum] = empty{} + self.sampleChain[seqNum] = futureSeqNum +} + +func (self *chainSampler) expireAndReplace() { + expSeqNum := self.numObservations - self.windowSize + if _, ok := self.samples[expSeqNum]; !ok { + // No sample expires + return + } + delete(self.samples, expSeqNum) + // There must be a replacement, otherwise panic. + replacementSeqNum := self.sampleChain[expSeqNum] + // The sequence number must increase by one for each observation. + replacement, ok := self.replacements[replacementSeqNum] + if !ok { + log.Printf("cannot find %v. which is the replacement of %v\n", replacementSeqNum, expSeqNum) + pretty.Printf("chain: %# v\n", self) + panic("Should never occur!") + } + // This observation must have arrived before. + self.samples[replacementSeqNum] = replacement +} + +func (self *chainSampler) Update(obv interface{}) { + self.lock.Lock() + defer self.lock.Unlock() + + self.numObservations++ + self.arrive(self.numObservations, obv) + self.expireAndReplace() +} + +func (self *chainSampler) Len() int { + self.lock.RLock() + defer self.lock.RUnlock() + return len(self.samples) +} + +func (self *chainSampler) Reset() { + self.lock.Lock() + defer self.lock.Unlock() + self.numObservations = 0 + self.samples = make(map[int64]interface{}, self.sampleSize) + self.futureSamples = make(map[int64]empty, self.sampleSize*2) + self.sampleChain = make(map[int64]int64, self.sampleSize*2) + self.replacements = make(map[int64]interface{}, self.sampleSize*2) + self.initFutureSamples() +} + +func (self *chainSampler) Map(f func(d interface{})) { + self.lock.RLock() + defer self.lock.RUnlock() + + for seqNum, obv := range self.samples { + if _, ok := obv.(int); !ok { + pretty.Printf("Seq %v. WAT: %# v\n", seqNum, obv) + } + f(obv) + } +} + +// NOT SUPPORTED +func (self *chainSampler) Filter(filter func(d interface{}) bool) { + return +} + +// Chain sampler described in +// Brian Babcok, Mayur Datar and Rajeev Motwani, +// Sampling From a Moving Window Over Streaming Data +func NewChainSampler(sampleSize, windowSize int) Sampler { + sampler := &chainSampler{ + sampleSize: sampleSize, + windowSize: int64(windowSize), + samples: make(map[int64]interface{}, sampleSize), + futureSamples: make(map[int64]empty, sampleSize*2), + sampleChain: make(map[int64]int64, sampleSize*2), + replacements: make(map[int64]interface{}, sampleSize*2), + } + sampler.initFutureSamples() + return sampler +} diff --git a/sampling/chainsample_test.go b/sampling/chainsample_test.go new file mode 100644 index 00000000..83cbca68 --- /dev/null +++ b/sampling/chainsample_test.go @@ -0,0 +1,29 @@ +package sampling + +import "testing" + +func TestChainSampler(t *testing.T) { + numSamples := 10 + windowSize := 10 * numSamples + numObservations := 10 * windowSize + numSampleRounds := 10 * numObservations + + s := NewChainSampler(numSamples, windowSize) + hist := make(map[int]int, numSamples) + for i := 0; i < numSampleRounds; i++ { + sampleStream(hist, numObservations, s) + } + ratio := histStddev(hist) / histMean(hist) + if ratio > 1.05 { + // XXX(dengnan): better sampler? + t.Errorf("std dev: %v; mean: %v. Either we have a really bad PRNG, or a bad implementation", histStddev(hist), histMean(hist)) + } + if len(hist) > windowSize { + t.Errorf("sampled %v data. larger than window size %v", len(hist), windowSize) + } + for seqNum, freq := range hist { + if seqNum < numObservations-windowSize && freq > 0 { + t.Errorf("observation with seqnum %v is sampled %v times", seqNum, freq) + } + } +} diff --git a/sampling/doc.go b/sampling/doc.go new file mode 100644 index 00000000..8eeb6c47 --- /dev/null +++ b/sampling/doc.go @@ -0,0 +1,17 @@ +// Copyright 2014 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 sampling provides several sampling algorithms. +// These algorithms will be used to sample containers' stats information +package sampling diff --git a/sampling/es.go b/sampling/es.go new file mode 100644 index 00000000..64f40be1 --- /dev/null +++ b/sampling/es.go @@ -0,0 +1,143 @@ +// Copyright 2014 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 sampling + +import ( + "container/heap" + "math" + "math/rand" + "sync" +) + +type esSampleItem struct { + data interface{} + key float64 +} + +type esSampleHeap []esSampleItem + +func (self esSampleHeap) Len() int { + return len(self) +} + +func (self esSampleHeap) Less(i, j int) bool { + return self[i].key < self[j].key +} + +func (self esSampleHeap) Swap(i, j int) { + self[i], self[j] = self[j], self[i] +} + +func (self *esSampleHeap) Push(x interface{}) { + item := x.(esSampleItem) + *self = append(*self, item) +} + +func (self *esSampleHeap) Pop() interface{} { + old := *self + item := old[len(old)-1] + *self = old[:len(old)-1] + return item +} + +type esSampler struct { + weight func(interface{}) float64 + samples *esSampleHeap + maxSize int + lock sync.RWMutex +} + +func (self *esSampler) Update(d interface{}) { + self.lock.Lock() + defer self.lock.Unlock() + + u := rand.Float64() + key := math.Pow(u, 1.0/self.weight(d)) + + if self.samples.Len() < self.maxSize { + heap.Push(self.samples, esSampleItem{ + data: d, + key: key, + }) + return + } + + s := *(self.samples) + min := s[0] + + // The key of the new item is larger than a key in existing item. + // Add this new item. + if key > min.key { + heap.Pop(self.samples) + heap.Push(self.samples, esSampleItem{ + data: d, + key: key, + }) + } +} + +func (self *esSampler) Len() int { + self.lock.RLock() + defer self.lock.RUnlock() + return len(*self.samples) +} + +func (self *esSampler) Reset() { + self.lock.Lock() + defer self.lock.Unlock() + self.samples = &esSampleHeap{} + heap.Init(self.samples) +} + +func (self *esSampler) Map(f func(interface{})) { + self.lock.RLock() + defer self.lock.RUnlock() + + for _, d := range *self.samples { + f(d.data) + } +} + +func (self *esSampler) Filter(filter func(d interface{}) bool) { + self.lock.Lock() + defer self.lock.Unlock() + + rmlist := make([]int, 0, len(*self.samples)) + for i, d := range *self.samples { + if filter(d.data) { + rmlist = append(rmlist, i) + } + } + + for _, i := range rmlist { + heap.Remove(self.samples, i) + } +} + +// ES sampling algorithm described in +// +// Pavlos S. Efraimidis and Paul G. Spirakis. Weighted random sampling with a +// reservoir. Information Processing Letters, 97(5):181 – 185, 2006. +// +// http://dl.acm.org/citation.cfm?id=1138834 +func NewESSampler(size int, weight func(interface{}) float64) Sampler { + s := &esSampleHeap{} + heap.Init(s) + return &esSampler{ + maxSize: size, + samples: s, + weight: weight, + } +} diff --git a/sampling/es_test.go b/sampling/es_test.go new file mode 100644 index 00000000..d45b2762 --- /dev/null +++ b/sampling/es_test.go @@ -0,0 +1,81 @@ +// Copyright 2014 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 sampling + +import ( + "container/heap" + "math/rand" + "testing" + + "github.com/kr/pretty" +) + +// This should be a min heap +func TestESSampleHeap(t *testing.T) { + h := &esSampleHeap{} + heap.Init(h) + min := 5.0 + N := 10 + + for i := 0; i < N; i++ { + key := rand.Float64() + if key < min { + min = key + } + heap.Push(h, esSampleItem{nil, key}) + } + l := *h + if l[0].key != min { + t.Errorf("not a min heap") + pretty.Printf("min=%v\nheap=%# v\n", min, l) + } +} + +func TestESSampler(t *testing.T) { + reservoirSize := 10 + numObvs := 10 * reservoirSize + numSampleRounds := 100 * numObvs + + weight := func(d interface{}) float64 { + n := d.(int) + return float64(n + 1) + } + s := NewESSampler(reservoirSize, weight) + hist := make(map[int]int, numObvs) + for i := 0; i < numSampleRounds; i++ { + sampleStream(hist, numObvs, s) + } + + diff := 2 + wrongOrderedItems := make([]int, 0, numObvs) + threshold := 1.05 + for i := 0; i < numObvs-diff; i++ { + // Item with smaller weight should have lower probability to be selected. + n1 := hist[i] + n2 := hist[i+diff] + if n1 > n2 { + if float64(n1) > float64(n2)*threshold { + wrongOrderedItems = append(wrongOrderedItems, i) + } + } + } + if float64(len(wrongOrderedItems)) > float64(numObvs)*0.05 { + for _, i := range wrongOrderedItems { + n1 := hist[i] + n2 := hist[i+diff] + t.Errorf("item with weight %v is selected %v times; while item with weight %v is selected %v times", i, n1, i+diff, n2) + } + } +} diff --git a/sampling/reservoir.go b/sampling/reservoir.go new file mode 100644 index 00000000..fc76e103 --- /dev/null +++ b/sampling/reservoir.go @@ -0,0 +1,99 @@ +// Copyright 2014 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 sampling + +import ( + "math/rand" + "sync" +) + +// Reservoir sampling algorithm. +// http://en.wikipedia.org/wiki/Reservoir_sampling +type reservoirSampler struct { + maxSize int + samples []interface{} + numInstances int64 + lock sync.RWMutex +} + +func (self *reservoirSampler) Len() int { + self.lock.RLock() + defer self.lock.RUnlock() + return len(self.samples) +} + +func (self *reservoirSampler) Reset() { + self.lock.Lock() + defer self.lock.Unlock() + self.samples = make([]interface{}, 0, self.maxSize) + self.numInstances = 0 +} + +// Update samples according to http://en.wikipedia.org/wiki/Reservoir_sampling +func (self *reservoirSampler) Update(d interface{}) { + self.lock.Lock() + defer self.lock.Unlock() + + self.numInstances++ + if len(self.samples) < self.maxSize { + self.samples = append(self.samples, d) + return + } + // Randomly generates a number between [0, numInances). + // Use this random number, j, as an index. If j is larger than the + // reservoir size, we will ignore the current new data. + // Otherwise replace the jth element in reservoir with the new data. + j := rand.Int63n(self.numInstances) + if j < int64(len(self.samples)) { + self.samples[int(j)] = d + } +} + +func (self *reservoirSampler) Map(f func(d interface{})) { + self.lock.RLock() + defer self.lock.RUnlock() + + for _, d := range self.samples { + f(d) + } +} + +// Once an element is removed, the probability of sampling an observation will +// be increased. Removing all elements in the sampler has the same effect as +// calling Reset(). However, it will not guarantee the uniform probability of +// all unfiltered samples. +func (self *reservoirSampler) Filter(filter func(d interface{}) bool) { + self.lock.Lock() + defer self.lock.Unlock() + rmlist := make([]int, 0, len(self.samples)) + for i, d := range self.samples { + if filter(d) { + rmlist = append(rmlist, i) + } + } + + for _, i := range rmlist { + // slice trick: remove the ith element without preserving the order + self.samples[i] = self.samples[len(self.samples)-1] + self.samples = self.samples[:len(self.samples)-1] + } + self.numInstances -= int64(len(rmlist)) +} + +func NewReservoirSampler(reservoirSize int) Sampler { + return &reservoirSampler{ + maxSize: reservoirSize, + } +} diff --git a/sampling/reservoir_test.go b/sampling/reservoir_test.go new file mode 100644 index 00000000..1113b363 --- /dev/null +++ b/sampling/reservoir_test.go @@ -0,0 +1,70 @@ +// Copyright 2014 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 sampling + +import ( + "math" + "testing" +) + +func sampleStream(hist map[int]int, n int, s Sampler) { + s.Reset() + for i := 0; i < n; i++ { + s.Update(i) + } + s.Map(func(d interface{}) { + j := d.(int) + if _, ok := hist[j]; !ok { + hist[j] = 0 + } + hist[j]++ + }) +} + +func histMean(hist map[int]int) float64 { + total := 0 + for _, v := range hist { + total += v + } + return float64(total) / float64(len(hist)) +} + +func histStddev(hist map[int]int) float64 { + mean := histMean(hist) + var totalDiff float64 + for _, v := range hist { + diff := float64(v) - mean + sq := diff * diff + totalDiff += sq + } + return math.Sqrt(totalDiff / float64(len(hist))) +} + +// XXX(dengnan): This test may take more than 10 seconds. +func TestReservoirSampler(t *testing.T) { + reservoirSize := 10 + numSamples := 10 * reservoirSize + numSampleRounds := 100 * numSamples + + s := NewReservoirSampler(reservoirSize) + hist := make(map[int]int, numSamples) + for i := 0; i < numSampleRounds; i++ { + sampleStream(hist, numSamples, s) + } + ratio := histStddev(hist) / histMean(hist) + if ratio > 0.05 { + t.Errorf("std dev: %v; mean: %v. Either we have a really bad PRNG, or a bad implementation", histStddev(hist), histMean(hist)) + } +} diff --git a/sampling/sampler.go b/sampling/sampler.go new file mode 100644 index 00000000..4e81ac55 --- /dev/null +++ b/sampling/sampler.go @@ -0,0 +1,42 @@ +// Copyright 2014 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 sampling + +import ( + crand "crypto/rand" + "encoding/binary" + "math/rand" +) + +func init() { + // NOTE(dengnan): Even if we picked a good random seed, + // the random number from math/rand is still not cryptographically secure! + var seed int64 + binary.Read(crand.Reader, binary.LittleEndian, &seed) + rand.Seed(seed) +} + +type Sampler interface { + Update(d interface{}) + Len() int + Reset() + Map(f func(interface{})) + + // Filter() should update in place. Removing elements may or may not + // affect the statistical behavior of the sampler, i.e. the probability + // that an obervation will be sampled after removing some elements is + // implementation defined. + Filter(filter func(interface{}) bool) +}