diff --git a/pkg/api/types.go b/pkg/api/types.go index ec021abb4f0..36ce6247653 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -68,6 +68,20 @@ type Container struct { VolumeMounts []VolumeMount `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"` } +// Percentile represents a pair which contains a percentage from 0 to 100 and +// its corresponding value. +type Percentile struct { + Percentage int `json:"percentage,omitempty"` + Value uint64 `json:"value,omitempty"` +} + +// ContainerStats represents statistical information of a container +type ContainerStats struct { + CpuUsagePercentiles []Percentile `json:"cpu_usage_percentiles,omitempty"` + MemoryUsagePercentiles []Percentile `json:"memory_usage_percentiles,omitempty"` + MaxMemoryUsage uint64 `json:"max_memory_usage,omitempty"` +} + // Event is the representation of an event logged to etcd backends type Event struct { Event string `json:"event,omitempty"` diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index e4fa2e2146a..96d358ba078 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -36,6 +36,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/coreos/go-etcd/etcd" "github.com/fsouza/go-dockerclient" + "github.com/google/cadvisor/info" "gopkg.in/v1/yaml" ) @@ -58,11 +59,17 @@ type DockerInterface interface { StopContainer(id string, timeout uint) error } +type CadvisorInterface interface { + ContainerInfo(name string) (*info.ContainerInfo, error) + MachineInfo() (*info.MachineInfo, error) +} + // The main kubelet implementation type Kubelet struct { Hostname string Client util.EtcdClient DockerClient DockerInterface + CadvisorClient CadvisorInterface FileCheckFrequency time.Duration SyncFrequency time.Duration HTTPCheckFrequency time.Duration @@ -641,3 +648,43 @@ func (kl *Kubelet) GetContainerInfo(name string) (string, error) { data, err := json.Marshal(info) return string(data), err } + +func (kl *Kubelet) GetContainerStats(name string) (*api.ContainerStats, error) { + if kl.CadvisorClient == nil { + return nil, nil + } + id, found, err := kl.GetContainerID(name) + if err != nil || !found { + return nil, err + } + + info, err := kl.CadvisorClient.ContainerInfo(fmt.Sprintf("/docker/%v", id)) + + if err != nil { + return nil, err + } + // When the stats data for the container is not available yet. + if info.StatsPercentiles == nil { + return nil, nil + } + + ret := new(api.ContainerStats) + ret.MaxMemoryUsage = info.StatsPercentiles.MaxMemoryUsage + if len(info.StatsPercentiles.CpuUsagePercentiles) > 0 { + percentiles := make([]api.Percentile, len(info.StatsPercentiles.CpuUsagePercentiles)) + for i, p := range info.StatsPercentiles.CpuUsagePercentiles { + percentiles[i].Percentage = p.Percentage + percentiles[i].Value = p.Value + } + ret.CpuUsagePercentiles = percentiles + } + if len(info.StatsPercentiles.MemoryUsagePercentiles) > 0 { + percentiles := make([]api.Percentile, len(info.StatsPercentiles.MemoryUsagePercentiles)) + for i, p := range info.StatsPercentiles.MemoryUsagePercentiles { + percentiles[i].Percentage = p.Percentage + percentiles[i].Value = p.Value + } + ret.MemoryUsagePercentiles = percentiles + } + return ret, nil +} diff --git a/pkg/kubelet/kubelet_server.go b/pkg/kubelet/kubelet_server.go index 943e0ac20ae..04899cee38c 100644 --- a/pkg/kubelet/kubelet_server.go +++ b/pkg/kubelet/kubelet_server.go @@ -17,6 +17,7 @@ limitations under the License. package kubelet import ( + "encoding/json" "fmt" "io/ioutil" "net/http" @@ -35,6 +36,7 @@ type KubeletServer struct { // For testablitiy. type kubeletInterface interface { GetContainerID(name string) (string, bool, error) + GetContainerStats(name string) (*api.ContainerStats, error) GetContainerInfo(name string) (string, error) } @@ -64,6 +66,33 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } s.UpdateChannel <- manifest + case u.Path == "/containerStats": + container := u.Query().Get("container") + if len(container) == 0 { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprint(w, "Missing container query arg.") + return + } + stats, err := s.Kubelet.GetContainerStats(container) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Internal Error: %#v", err) + return + } + if stats == nil { + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, "{}") + return + } + data, err := json.Marshal(stats) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Internal Error: %#v", err) + return + } + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-type", "application/json") + w.Write(data) case u.Path == "/containerInfo": container := u.Query().Get("container") if len(container) == 0 { diff --git a/pkg/kubelet/kubelet_server_test.go b/pkg/kubelet/kubelet_server_test.go index ac9ddd4c3a6..ba2a3531f46 100644 --- a/pkg/kubelet/kubelet_server_test.go +++ b/pkg/kubelet/kubelet_server_test.go @@ -2,6 +2,7 @@ package kubelet import ( "bytes" + "encoding/json" "fmt" "io/ioutil" "net/http" @@ -15,8 +16,9 @@ import ( ) type fakeKubelet struct { - infoFunc func(name string) (string, error) - idFunc func(name string) (string, bool, error) + infoFunc func(name string) (string, error) + idFunc func(name string) (string, bool, error) + statsFunc func(name string) (*api.ContainerStats, error) } func (fk *fakeKubelet) GetContainerInfo(name string) (string, error) { @@ -27,6 +29,10 @@ func (fk *fakeKubelet) GetContainerID(name string) (string, bool, error) { return fk.idFunc(name) } +func (fk *fakeKubelet) GetContainerStats(name string) (*api.ContainerStats, error) { + return fk.statsFunc(name) +} + // If we made everything distribute a list of ContainerManifests, we could just use // channelReader. type channelReaderSingle struct { @@ -129,3 +135,42 @@ func TestContainerInfo(t *testing.T) { t.Errorf("Expected: '%v', got: '%v'", expected, got) } } + +func TestContainerStats(t *testing.T) { + fw := makeServerTest() + expectedStats := &api.ContainerStats{ + MaxMemoryUsage: 1024001, + CpuUsagePercentiles: []api.Percentile{ + {50, 150}, + {80, 180}, + {90, 190}, + }, + MemoryUsagePercentiles: []api.Percentile{ + {50, 150}, + {80, 180}, + {90, 190}, + }, + } + expectedContainerName := "goodcontainer" + fw.fakeKubelet.statsFunc = func(name string) (*api.ContainerStats, error) { + if name != expectedContainerName { + return nil, fmt.Errorf("bad container name: %v", name) + } + return expectedStats, nil + } + + resp, err := http.Get(fw.testHttpServer.URL + fmt.Sprintf("/containerStats?container=%v", expectedContainerName)) + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + var receivedStats api.ContainerStats + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(&receivedStats) + if err != nil { + t.Fatalf("received invalid json data: %v", err) + } + if !reflect.DeepEqual(&receivedStats, expectedStats) { + t.Errorf("received wrong data: %#v", receivedStats) + } +} diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 45541fabaf4..64fc2d02851 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -29,6 +29,8 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/coreos/go-etcd/etcd" "github.com/fsouza/go-dockerclient" + "github.com/google/cadvisor/info" + "github.com/stretchr/testify/mock" ) // TODO: This doesn't reduce typing enough to make it worth the less readable errors. Remove. @@ -905,3 +907,183 @@ func TestWatchEtcd(t *testing.T) { t.Errorf("Unexpected manifest(s) %#v %#v", read[0], manifest) } } + +type mockCadvisorClient struct { + mock.Mock +} + +func (self *mockCadvisorClient) ContainerInfo(name string) (*info.ContainerInfo, error) { + args := self.Called(name) + return args.Get(0).(*info.ContainerInfo), args.Error(1) +} + +func (self *mockCadvisorClient) MachineInfo() (*info.MachineInfo, error) { + args := self.Called() + return args.Get(0).(*info.MachineInfo), args.Error(1) +} + +func areSamePercentiles( + cadvisorPercentiles []info.Percentile, + kubePercentiles []api.Percentile, + t *testing.T, +) { + if len(cadvisorPercentiles) != len(kubePercentiles) { + t.Errorf("cadvisor gives %v percentiles; kubelet got %v", len(cadvisorPercentiles), len(kubePercentiles)) + return + } + for _, ap := range cadvisorPercentiles { + found := false + for _, kp := range kubePercentiles { + if ap.Percentage == kp.Percentage { + found = true + if ap.Value != kp.Value { + t.Errorf("%v percentile from cadvisor is %v; kubelet got %v", + ap.Percentage, + ap.Value, + kp.Value) + } + } + } + if !found { + t.Errorf("Unable to find %v percentile in kubelet's data", ap.Percentage) + } + } +} + +func TestGetContainerStats(t *testing.T) { + containerId := "ab2cdf" + containerPath := fmt.Sprintf("/docker/%v", containerId) + containerInfo := &info.ContainerInfo{ + ContainerReference: info.ContainerReference{ + Name: containerPath, + }, + StatsPercentiles: &info.ContainerStatsPercentiles{ + MaxMemoryUsage: 1024000, + MemoryUsagePercentiles: []info.Percentile{ + {50, 100}, + {80, 180}, + {90, 190}, + }, + CpuUsagePercentiles: []info.Percentile{ + {51, 101}, + {81, 181}, + {91, 191}, + }, + }, + } + fakeDocker := FakeDockerClient{ + err: nil, + } + + mockCadvisor := &mockCadvisorClient{} + mockCadvisor.On("ContainerInfo", containerPath).Return(containerInfo, nil) + + kubelet := Kubelet{ + DockerClient: &fakeDocker, + CadvisorClient: mockCadvisor, + } + fakeDocker.containerList = []docker.APIContainers{ + { + Names: []string{"foo"}, + ID: containerId, + }, + } + + stats, err := kubelet.GetContainerStats("foo") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if stats.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage { + t.Errorf("wrong max memory usage") + } + areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.CpuUsagePercentiles, t) + areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.MemoryUsagePercentiles, t) + mockCadvisor.AssertExpectations(t) +} + +func TestGetContainerStatsWithoutCadvisor(t *testing.T) { + fakeDocker := FakeDockerClient{ + err: nil, + } + + kubelet := Kubelet{ + DockerClient: &fakeDocker, + } + fakeDocker.containerList = []docker.APIContainers{ + { + Names: []string{"foo"}, + }, + } + + stats, _ := kubelet.GetContainerStats("foo") + // When there's no cAdvisor, the stats should be either nil or empty + if stats == nil { + return + } + if stats.MaxMemoryUsage != 0 { + t.Errorf("MaxMemoryUsage is %v even if there's no cadvisor", stats.MaxMemoryUsage) + } + if len(stats.CpuUsagePercentiles) > 0 { + t.Errorf("Cpu usage percentiles is not empty (%+v) even if there's no cadvisor", stats.CpuUsagePercentiles) + } + if len(stats.MemoryUsagePercentiles) > 0 { + t.Errorf("Memory usage percentiles is not empty (%+v) even if there's no cadvisor", stats.MemoryUsagePercentiles) + } +} + +func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) { + containerId := "ab2cdf" + containerPath := fmt.Sprintf("/docker/%v", containerId) + fakeDocker := FakeDockerClient{ + err: nil, + } + + containerInfo := &info.ContainerInfo{} + mockCadvisor := &mockCadvisorClient{} + expectedErr := fmt.Errorf("some error") + mockCadvisor.On("ContainerInfo", containerPath).Return(containerInfo, expectedErr) + + kubelet := Kubelet{ + DockerClient: &fakeDocker, + CadvisorClient: mockCadvisor, + } + fakeDocker.containerList = []docker.APIContainers{ + { + Names: []string{"foo"}, + ID: containerId, + }, + } + + stats, err := kubelet.GetContainerStats("foo") + if stats != nil { + t.Errorf("non-nil stats on error") + } + if err == nil { + t.Errorf("expect error but received nil error") + return + } + if err.Error() != expectedErr.Error() { + t.Errorf("wrong error message. expect %v, got %v", err, expectedErr) + } + mockCadvisor.AssertExpectations(t) +} + +func TestGetContainerStatsOnNonExistContainer(t *testing.T) { + fakeDocker := FakeDockerClient{ + err: nil, + } + + mockCadvisor := &mockCadvisorClient{} + + kubelet := Kubelet{ + DockerClient: &fakeDocker, + CadvisorClient: mockCadvisor, + } + fakeDocker.containerList = []docker.APIContainers{} + + stats, _ := kubelet.GetContainerStats("foo") + if stats != nil { + t.Errorf("non-nil stats on non exist container") + } + mockCadvisor.AssertExpectations(t) +} diff --git a/third_party/deps.sh b/third_party/deps.sh index 1391af1b0b6..8f489f17475 100755 --- a/third_party/deps.sh +++ b/third_party/deps.sh @@ -12,6 +12,7 @@ DEP_PACKAGES=" code.google.com/p/google-api-go-client/googleapi github.com/coreos/go-log/log github.com/coreos/go-systemd/journal + github.com/google/cadvisor/info " PACKAGES="$TOP_PACKAGES $DEP_PACKAGES" diff --git a/third_party/src/github.com/google/cadvisor/.travis.yml b/third_party/src/github.com/google/cadvisor/.travis.yml new file mode 100644 index 00000000000..8542bd19106 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - 1.2 +before_script: + - go get github.com/stretchr/testify/mock + - go get github.com/kr/pretty +script: + - go test -v -race github.com/google/cadvisor/container + - go test -v github.com/google/cadvisor/info + - go test -v github.com/google/cadvisor/client + - go test -v github.com/google/cadvisor/sampling + - go test -v github.com/google/cadvisor/storage/memory + - go build github.com/google/cadvisor diff --git a/third_party/src/github.com/google/cadvisor/AUTHORS b/third_party/src/github.com/google/cadvisor/AUTHORS new file mode 100644 index 00000000000..f4aa3d1301f --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/CONTRIBUTING.md b/third_party/src/github.com/google/cadvisor/CONTRIBUTING.md new file mode 100644 index 00000000000..60e926beed3 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/CONTRIBUTING.md @@ -0,0 +1,14 @@ +## How to contribute + +Open an issue or a pull request, its that easy! + +## Contributor License Agreements + +We'd love to accept your pull requests! Before we can merge them, we have to jump a couple of legal hurdles. + +Please fill out either the individual or corporate Contributor License Agreement (CLA). + + * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html). + * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html). + +Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. diff --git a/third_party/src/github.com/google/cadvisor/CONTRIBUTORS b/third_party/src/github.com/google/cadvisor/CONTRIBUTORS new file mode 100644 index 00000000000..d0c97f1ad9f --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/Dockerfile b/third_party/src/github.com/google/cadvisor/Dockerfile new file mode 100644 index 00000000000..f5b1517065d --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/Dockerfile @@ -0,0 +1,16 @@ +FROM google/golang-runtime +MAINTAINER dengnan@google.com vmarmol@google.com proppy@google.com + +# TODO(vmarmol): Build from source. +# Get lmctfy and its dependencies. +RUN apt-get update -y --force-yes && apt-get install -y --no-install-recommends --force-yes pkg-config libapparmor1 +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0 +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/lmctfy /usr/bin/lmctfy +RUN chmod +x /usr/bin/lmctfy + +# Install libprotobuf8. +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libprotobuf8_2.5.0-9_amd64.deb /tmp/libprotobuf8_2.5.0-9_amd64.deb +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libc6_2.19-1_amd64.deb /tmp/libc6_2.19-1_amd64.deb +RUN dpkg -i /tmp/libc6_2.19-1_amd64.deb /tmp/libprotobuf8_2.5.0-9_amd64.deb + +# The image builds the app and exposes it on 8080. diff --git a/third_party/src/github.com/google/cadvisor/LICENSE b/third_party/src/github.com/google/cadvisor/LICENSE new file mode 100644 index 00000000000..97cec18e875 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/README.md b/third_party/src/github.com/google/cadvisor/README.md new file mode 100644 index 00000000000..5e4e65a828e --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/README.md @@ -0,0 +1,93 @@ +# cAdvisor + +cAdvisor (Container Advisor) provides container users an understanding of the resource usage and performance characteristics of their running containers. It is a running daemon that collects, aggregates, processes, and exports information about running containers. Specifically, for each container it keeps resource isolation parameters, historical resource usage, and histograms of complete historical resource usage. This data is exported by container and machine-wide. + +cAdvisor currently supports lmctfy containers as well as Docker containers (those that use the default libcontainer execdriver). Other container backends can also be added. cAdvisor's container abstraction is based on lmctfy's so containers are inherently nested hierarchically. + +![cAdvisor](logo.png "cAdvisor") + +#### Quick Start: Running cAdvisor in a Docker Container + +To quickly tryout cAdvisor on your machine with Docker (version 0.11 or above), we have a Docker image that includes everything you need to get started. Simply run: + +``` +sudo docker run \ + --volume=/var/run:/var/run:rw \ + --volume=/sys/fs/cgroup/:/sys/fs/cgroup:ro \ + --volume=/var/lib/docker/:/var/lib/docker:ro \ + --publish=8080:8080 \ + --detach=true \ + google/cadvisor +``` + +cAdvisor is now running (in the background) on `http://localhost:8080`. The setup includes directories with Docker state cAdvisor needs to observe. + +If you want to build your own cAdvisor Docker image, take a look at the Dockerfile which can build a cAdvisor docker container under `quickstart/Dockerfile`. + +## Web UI + +cAdvisor exposes a web UI at its port: + +`http://:/` + +## Remote REST API + +cAdvisor exposes its raw and processed stats via a versioned remote REST API: + +`http://:/api//` + +The current (and only) version of the API is `v1.0`. + +### Version 1.0 + +This version exposes two main endpoints, one for container information and the other for machine information. Both endpoints are read-only in v1.0. + +#### Container Information + +The resource name for container information is as follows: + +`/api/v1.0/containers/` + +Where the absolute container name follows the lmctfy naming convention. For example: + +| Container Name | Resource Name | +|----------------------|-------------------------------------------| +| / | /api/v1.0/containers/ | +| /foo | /api/v1.0/containers/foo | +| /docker/2c4dee605d22 | /api/v1.0/containers/docker/2c4dee605d22 | + +Note that the root container (`/`) contains usage for the entire machine. All Docker containers are listed under `/docker`. + +The container information is returned as a JSON object containing: + +- Absolute container name +- List of subcontainers +- ContainerSpec which describes the resource isolation enabled in the container +- Detailed resource usage statistics of the container for the last `N` seconds (`N` is globally configurable in cAdvisor) +- Histogram of resource usage from the creation of the container + +The actual object is the marshalled JSON of the `ContainerInfo` struct found in [info/container.go](info/container.go) + +#### Machine Information + +The resource name for machine information is as follows: + +`/api/v1.0/machine` + +This resource is read-only. The machine information is returned as a JSON object containing: + +- Number of schedulable logical CPU cores +- Memory capacity (in bytes) + +The actual object is the marshalled JSON of the `MachineInfo` struct found in [info/machine.go](info/machine.go) + +## Roadmap + +cAdvisor aims to improve the resource usage and performance characteristics of running containers. Today, we gather and expose this information to users. In our roadmap: +- Advise on the performance of a container (e.g.: when it is being negatively affected by another, when it is not receiving the resources it requires, etc) +- Auto-tune the performance of the container based on previous advise. +- Provide usage prediction to cluster schedulers and orchestration layers. + +## Community + +Contributions, questions, and comments are all welcomed and encouraged! cAdvisor developers hand out in [#google-containers](http://webchat.freenode.net/?channels=google-containers) room on freenode.net. We also have the [google-containers Google Groups mailing list](https://groups.google.com/forum/#!forum/google-containers). diff --git a/third_party/src/github.com/google/cadvisor/TODO.txt b/third_party/src/github.com/google/cadvisor/TODO.txt new file mode 100644 index 00000000000..b8a6815e286 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/api/handler.go b/third_party/src/github.com/google/cadvisor/api/handler.go new file mode 100644 index 00000000000..409dc917791 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/cadvisor.go b/third_party/src/github.com/google/cadvisor/cadvisor.go new file mode 100644 index 00000000000..b6fa9f917b5 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/cadvisor.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 main + +import ( + "flag" + "fmt" + "log" + "net/http" + + "github.com/google/cadvisor/api" + "github.com/google/cadvisor/container/docker" + "github.com/google/cadvisor/container/lmctfy" + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/manager" + "github.com/google/cadvisor/pages" + "github.com/google/cadvisor/pages/static" + "github.com/google/cadvisor/storage/memory" +) + +var argPort = flag.Int("port", 8080, "port to listen") +var argSampleSize = flag.Int("samples", 1024, "number of samples we want to keep") +var argHistoryDuration = flag.Int("history_duration", 60, "number of seconds of container history to keep") + +func main() { + flag.Parse() + + storage := memory.New(*argSampleSize, *argHistoryDuration) + // TODO(monnand): Add stats writer for manager + containerManager, err := manager.New(storage) + 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.Printf("Starting cAdvisor version: %q", info.VERSION) + log.Print("About to serve on port ", *argPort) + + addr := fmt.Sprintf(":%v", *argPort) + log.Fatal(http.ListenAndServe(addr, nil)) +} diff --git a/third_party/src/github.com/google/cadvisor/client/client.go b/third_party/src/github.com/google/cadvisor/client/client.go new file mode 100644 index 00000000000..d521b1e3fa8 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/client/client_test.go b/third_party/src/github.com/google/cadvisor/client/client_test.go new file mode 100644 index 00000000000..d02607302b4 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/client/client_test.go @@ -0,0 +1,756 @@ +// 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", + "spec": { + "cpu": { + "limit": 18446744073709551000, + "max_limit": 0, + "mask": { + "data": [ + 18446744073709551000 + ] + } + }, + "memory": { + "limit": 18446744073709551000, + "swap_limit": 18446744073709551000 + } + }, + "stats": [ + { + "timestamp": "2014-06-13T01:03:26.434981825Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:27.538394608Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:28.640302072Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:29.74247308Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:30.844494537Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:31.946757066Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:33.050214062Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:34.15222186Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:35.25417391Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:36.355902169Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:37.457585928Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:38.559417379Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:39.662978029Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:40.764671232Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + }, + { + "timestamp": "2014-06-13T01:03:41.866456459Z", + "cpu": { + "usage": { + "total": 56896502, + "per_cpu": [ + 20479682, + 13579420, + 6025040, + 2255123, + 3635661, + 2489368, + 5158288, + 3273920 + ], + "user": 10000000, + "system": 30000000 + }, + "load": 0 + }, + "memory": { + "usage": 495616, + "container_data": { + "pgfault": 2279 + }, + "hierarchical_data": { + "pgfault": 2279 + } + } + } + ], + "stats_summary": { + "max_memory_usage": 495616, + "samples": [ + { + "timestamp": "2014-06-13T01:03:27.538394608Z", + "duration": 1103412783, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:28.640302072Z", + "duration": 1101907464, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:29.74247308Z", + "duration": 1102171008, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:30.844494537Z", + "duration": 1102021457, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:31.946757066Z", + "duration": 1102262529, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:33.050214062Z", + "duration": 1103456996, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:34.15222186Z", + "duration": 1102007798, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:35.25417391Z", + "duration": 1101952050, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:36.355902169Z", + "duration": 1101728259, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:37.457585928Z", + "duration": 1101683759, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:38.559417379Z", + "duration": 1101831451, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:39.662978029Z", + "duration": 1103560650, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:40.764671232Z", + "duration": 1101693203, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + }, + { + "timestamp": "2014-06-13T01:03:41.866456459Z", + "duration": 1101785227, + "cpu": { + "usage": 0 + }, + "memory": { + "usage": 495616 + } + } + ], + "memory_usage_percentiles": [ + { + "percentage": 50, + "value": 495616 + }, + { + "percentage": 80, + "value": 495616 + }, + { + "percentage": 90, + "value": 495616 + }, + { + "percentage": 95, + "value": 495616 + }, + { + "percentage": 99, + "value": 495616 + } + ], + "cpu_usage_percentiles": [ + { + "percentage": 50, + "value": 0 + }, + { + "percentage": 80, + "value": 0 + }, + { + "percentage": 90, + "value": 0 + }, + { + "percentage": 95, + "value": 0 + }, + { + "percentage": 99, + "value": 0 + } + ] + } +} +` + containerName := "/some/container" + respStr = fmt.Sprintf(respStr, 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/third_party/src/github.com/google/cadvisor/container/container.go b/third_party/src/github.com/google/cadvisor/container/container.go new file mode 100644 index 00000000000..de478fd0ac5 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/container.go @@ -0,0 +1,35 @@ +// 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 "github.com/google/cadvisor/info" + +// Listing types. +const ( + LIST_SELF = iota + LIST_RECURSIVE +) + +type ListType int + +// Interface for container operation handlers. +type ContainerHandler interface { + ContainerReference() (info.ContainerReference, error) + GetSpec() (*info.ContainerSpec, error) + GetStats() (*info.ContainerStats, error) + ListContainers(listType ListType) ([]info.ContainerReference, error) + ListThreads(listType ListType) ([]int, error) + ListProcesses(listType ListType) ([]int, error) +} diff --git a/third_party/src/github.com/google/cadvisor/container/docker/factory.go b/third_party/src/github.com/google/cadvisor/container/docker/factory.go new file mode 100644 index 00000000000..4d8165ab551 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/docker/factory.go @@ -0,0 +1,103 @@ +// 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, err = newDockerContainerHandler( + client, + name, + 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, + } + for _, p := range paths { + if p != "/" && p != "/docker" { + return fmt.Errorf("%v cannot be managed by docker", p) + } + container.RegisterContainerHandlerFactory(p, f) + } + return nil +} diff --git a/third_party/src/github.com/google/cadvisor/container/docker/handler.go b/third_party/src/github.com/google/cadvisor/container/docker/handler.go new file mode 100644 index 00000000000..5c8fc10bcc0 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/docker/handler.go @@ -0,0 +1,281 @@ +// 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/docker/libcontainer" + "github.com/docker/libcontainer/cgroups" + "github.com/docker/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 + aliases []string + machineInfoFactory info.MachineInfoFactory +} + +func newDockerContainerHandler( + client *docker.Client, + name string, + machineInfoFactory info.MachineInfoFactory, +) (container.ContainerHandler, error) { + handler := &dockerContainerHandler{ + client: client, + name: name, + machineInfoFactory: machineInfoFactory, + } + if !handler.isDockerContainer() { + return handler, nil + } + _, id, err := handler.splitName() + if err != nil { + return nil, fmt.Errorf("invalid docker container %v: %v", name, err) + } + ctnr, err := client.InspectContainer(id) + if err != nil { + return nil, fmt.Errorf("unable to inspect container %v: %v", name, err) + } + handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name)) + return handler, nil +} + +func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) { + return info.ContainerReference{ + Name: self.name, + Aliases: self.aliases, + }, nil +} + +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()) +} + +// TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API. +func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) { + dir := "/var/lib/docker/execdriver/native" + configPath := path.Join(dir, id, "container.json") + f, err := os.Open(configPath) + if err != nil { + return + } + defer f.Close() + d := json.NewDecoder(f) + ret := new(libcontainer.Container) + err = d.Decode(ret) + if err != nil { + return + } + spec = ret + return +} + +func libcontainerConfigToContainerSpec(config *libcontainer.Container, 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.Cgroups.Memory > 0 { + spec.Memory.Limit = uint64(config.Cgroups.Memory) + } + if config.Cgroups.MemorySwap > 0 { + spec.Memory.SwapLimit = uint64(config.Cgroups.MemorySwap) + } + + // Get CPU info + spec.Cpu = new(info.CpuSpec) + spec.Cpu.Limit = 1024 + if config.Cgroups.CpuShares != 0 { + spec.Cpu.Limit = uint64(config.Cgroups.CpuShares) + } + n := (mi.NumCores + 63) / 64 + spec.Cpu.Mask.Data = make([]uint64, n) + for i := 0; i < n; i++ { + spec.Cpu.Mask.Data[i] = math.MaxUint64 + } + // TODO(vmarmol): Get CPUs from config.Cgroups.CpusetCpus + 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 + } + libcontainerSpec, err := readLibcontainerSpec(id) + if err != nil { + return + } + + spec = libcontainerConfigToContainerSpec(libcontainerSpec, 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 empty stats for root containers. + stats = new(info.ContainerStats) + stats.Timestamp = time.Now() + 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) ([]info.ContainerReference, error) { + if self.isDockerContainer() { + return nil, nil + } + if self.isRootContainer() && listType == container.LIST_SELF { + return []info.ContainerReference{info.ContainerReference{Name: "/docker"}}, nil + } + opt := docker.ListContainersOptions{ + All: true, + } + containers, err := self.client.ListContainers(opt) + if err != nil { + return nil, err + } + ret := make([]info.ContainerReference, 0, len(containers)+1) + for _, c := range containers { + if !strings.HasPrefix(c.Status, "Up ") { + continue + } + path := fmt.Sprintf("/docker/%v", c.ID) + aliases := c.Names + ref := info.ContainerReference{ + Name: path, + Aliases: aliases, + } + ret = append(ret, ref) + } + if self.isRootContainer() { + ret = append(ret, info.ContainerReference{Name: "/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/third_party/src/github.com/google/cadvisor/container/factory.go b/third_party/src/github.com/google/cadvisor/container/factory.go new file mode 100644 index 00000000000..24f82ed1b52 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/container/factory_test.go b/third_party/src/github.com/google/cadvisor/container/factory_test.go new file mode 100644 index 00000000000..1a688417417 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/container/filter.go b/third_party/src/github.com/google/cadvisor/container/filter.go new file mode 100644 index 00000000000..4d2a8ce148e --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/filter.go @@ -0,0 +1,93 @@ +// 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 +} + +func (self *containerListFilter) ContainerReference() (info.ContainerReference, error) { + return self.handler.ContainerReference() +} + +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) ([]info.ContainerReference, error) { + containers, err := self.handler.ListContainers(listType) + if err != nil { + return nil, err + } + if len(containers) == 0 { + return nil, nil + } + ret := make([]info.ContainerReference, 0, len(containers)) + for _, c := range containers { + if self.filter(c.Name) { + 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/third_party/src/github.com/google/cadvisor/container/filter_test.go b/third_party/src/github.com/google/cadvisor/container/filter_test.go new file mode 100644 index 00000000000..5e8ef538d1e --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/filter_test.go @@ -0,0 +1,127 @@ +// 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 +} + +func (self *mockContainerHandler) GetSpec() (*info.ContainerSpec, error) { + args := self.Called() + return args.Get(0).(*info.ContainerSpec), args.Error(1) +} + +func (self *mockContainerHandler) ContainerReference() (info.ContainerReference, error) { + args := self.Called() + return args.Get(0).(info.ContainerReference), 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) ([]info.ContainerReference, error) { + args := self.Called(listType) + return args.Get(0).([]info.ContainerReference), 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( + []info.ContainerReference{ + info.ContainerReference{Name: "/docker/ee0103"}, + info.ContainerReference{Name: "/container/created/by/lmctfy"}, + info.ContainerReference{Name: "/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.Name, 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( + []info.ContainerReference{ + info.ContainerReference{Name: "/docker/ee0103"}, + info.ContainerReference{Name: "/container/created/by/lmctfy"}, + info.ContainerReference{Name: "/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.Name, prefix) { + legal = false + } + } + if !legal { + t.Errorf("%v is in the black list", c) + } + } + mockc.AssertExpectations(t) +} diff --git a/third_party/src/github.com/google/cadvisor/container/lmctfy/factory.go b/third_party/src/github.com/google/cadvisor/container/lmctfy/factory.go new file mode 100644 index 00000000000..104c6a68edc --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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 := &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/third_party/src/github.com/google/cadvisor/container/lmctfy/lmctfy.pb.go b/third_party/src/github.com/google/cadvisor/container/lmctfy/lmctfy.pb.go new file mode 100644 index 00000000000..d350d216020 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/container/lmctfy/lmctfy_container.go b/third_party/src/github.com/google/cadvisor/container/lmctfy/lmctfy_container.go new file mode 100644 index 00000000000..68b4147a7e9 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/lmctfy/lmctfy_container.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. + +// 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 +} + +const ( + lmctfyBinary = "lmctfy" + notFoundExitCode = 5 +) + +// Create a new +func New(name string) (container.ContainerHandler, error) { + el := &lmctfyContainerHandler{ + Name: name, + } + return el, nil +} + +func (self *lmctfyContainerHandler) ContainerReference() (info.ContainerReference, error) { + return info.ContainerReference{Name: self.Name}, 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) ([]info.ContainerReference, 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 nil, nil + } + names := strings.Split(string(data), "\n") + containerNames := make([]info.ContainerReference, 0, len(names)) + for _, name := range names { + if len(name) != 0 { + ref := info.ContainerReference{Name: name} + containerNames = append(containerNames, ref) + } + } + 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/third_party/src/github.com/google/cadvisor/container/lmctfy/virtual_host.pb.go b/third_party/src/github.com/google/cadvisor/container/lmctfy/virtual_host.pb.go new file mode 100644 index 00000000000..7c2f1485255 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/deploy/docker-only/Dockerfile b/third_party/src/github.com/google/cadvisor/deploy/docker-only/Dockerfile new file mode 100644 index 00000000000..cede96956fc --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/deploy/docker-only/Dockerfile @@ -0,0 +1,9 @@ +FROM busybox:ubuntu-14.04 +MAINTAINER kyurtsever@google.com dengnan@google.com vmarmol@google.com jason@swindle.me + +# Get cAdvisor binaries. +ADD http://storage.googleapis.com/cadvisor-bin/cadvisor /usr/bin/cadvisor +RUN chmod +x /usr/bin/cadvisor + +EXPOSE 8080 +ENTRYPOINT ["/usr/bin/cadvisor"] diff --git a/third_party/src/github.com/google/cadvisor/deploy/lmctfy-docker/Dockerfile b/third_party/src/github.com/google/cadvisor/deploy/lmctfy-docker/Dockerfile new file mode 100644 index 00000000000..ce9f76de324 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/deploy/lmctfy-docker/Dockerfile @@ -0,0 +1,14 @@ +FROM ubuntu:14.04 +MAINTAINER kyurtsever@google.com dengnan@google.com vmarmol@google.com + +# Get the lmctfy dependencies. +RUN apt-get update && apt-get install -y -q --no-install-recommends pkg-config libprotobuf8 libapparmor1 + +# 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/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0 +ADD http://storage.googleapis.com/cadvisor-bin/cadvisor /usr/bin/cadvisor +RUN chmod +x /usr/bin/lmctfy && chmod +x /usr/bin/cadvisor + +EXPOSE 8080 +ENTRYPOINT ["/usr/bin/cadvisor"] diff --git a/third_party/src/github.com/google/cadvisor/info/container.go b/third_party/src/github.com/google/cadvisor/info/container.go new file mode 100644 index 00000000000..94c7c6574c5 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/info/container.go @@ -0,0 +1,280 @@ +// 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 ( + "fmt" + "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"` +} + +// Container reference contains enough information to uniquely identify a container +type ContainerReference struct { + // The absolute name of the container. + Name string `json:"name"` + + Aliases []string `json:"aliases,omitempty"` +} + +type ContainerInfo struct { + ContainerReference + + // The direct subcontainers of the current container. + Subcontainers []ContainerReference `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"` + + // Randomly sampled container states. + Samples []*ContainerStatsSample `json:"samples,omitempty"` + + StatsPercentiles *ContainerStatsPercentiles `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 +} + +// All CPU usage metrics are cumulative from the creation of the container +type CpuStats struct { + Usage struct { + // Total CPU usage. + // Units: nanoseconds + Total uint64 `json:"total"` + + // Per CPU/core usage of the container. + // Unit: nanoseconds. + PerCpu []uint64 `json:"per_cpu,omitempty"` + + // Time spent in user space. + // Unit: nanoseconds + User uint64 `json:"user"` + + // Time spent in kernel space. + // Unit: nanoseconds + 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 memory. 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"` +} + +type ContainerStatsSample struct { + // Timetamp of the end of the sample period + Timestamp time.Time `json:"timestamp"` + // Duration of the sample period + Duration time.Duration `json:"duration"` + Cpu struct { + // number of nanoseconds of CPU time used by the container + Usage uint64 `json:"usage"` + } `json:"cpu"` + Memory struct { + // Units: Bytes. + Usage uint64 `json:"usage"` + } `json:"memory"` +} + +type Percentile struct { + Percentage int `json:"percentage"` + Value uint64 `json:"value"` +} + +type ContainerStatsPercentiles struct { + MaxMemoryUsage uint64 `json:"max_memory_usage,omitempty"` + MemoryUsagePercentiles []Percentile `json:"memory_usage_percentiles,omitempty"` + CpuUsagePercentiles []Percentile `json:"cpu_usage_percentiles,omitempty"` +} + +// Each sample needs two stats because the cpu usage in ContainerStats is +// cumulative. +// prev should be an earlier observation than current. +// This method is not thread/goroutine safe. +func NewSample(prev, current *ContainerStats) (*ContainerStatsSample, error) { + if prev == nil || current == nil { + return nil, fmt.Errorf("empty stats") + } + // Ignore this sample if it is incomplete + if prev.Cpu == nil || prev.Memory == nil || current.Cpu == nil || current.Memory == nil { + return nil, fmt.Errorf("incomplete stats") + } + // prev must be an early observation + if !current.Timestamp.After(prev.Timestamp) { + return nil, fmt.Errorf("wrong stats order") + } + // This data is invalid. + if current.Cpu.Usage.Total < prev.Cpu.Usage.Total { + return nil, fmt.Errorf("current CPU usage is less than prev CPU usage (cumulative).") + } + sample := new(ContainerStatsSample) + // Calculate the diff to get the CPU usage within the time interval. + sample.Cpu.Usage = current.Cpu.Usage.Total - prev.Cpu.Usage.Total + // Memory usage is current memory usage + sample.Memory.Usage = current.Memory.Usage + sample.Timestamp = current.Timestamp + sample.Duration = current.Timestamp.Sub(prev.Timestamp) + + return sample, nil +} + +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(requestedPercentiles ...int) []Percentile { + if len(self) == 0 { + return nil + } + ret := make([]Percentile, 0, len(requestedPercentiles)) + sort.Sort(self) + for _, p := range requestedPercentiles { + idx := (len(self) * p / 100) - 1 + if idx < 0 { + idx = 0 + } + ret = append( + ret, + Percentile{ + Percentage: p, + Value: self[idx], + }, + ) + } + return ret +} + +func NewPercentiles(samples []*ContainerStatsSample, cpuPercentages, memoryPercentages []int) *ContainerStatsPercentiles { + if len(samples) == 0 { + return nil + } + cpuUsages := make([]uint64, 0, len(samples)) + memUsages := make([]uint64, 0, len(samples)) + + for _, sample := range samples { + if sample == nil { + continue + } + cpuUsages = append(cpuUsages, sample.Cpu.Usage) + memUsages = append(memUsages, sample.Memory.Usage) + } + + ret := new(ContainerStatsPercentiles) + ret.CpuUsagePercentiles = uint64Slice(cpuUsages).Percentiles(cpuPercentages...) + ret.MemoryUsagePercentiles = uint64Slice(memUsages).Percentiles(memoryPercentages...) + return ret +} diff --git a/third_party/src/github.com/google/cadvisor/info/container_test.go b/third_party/src/github.com/google/cadvisor/info/container_test.go new file mode 100644 index 00000000000..4b275becc62 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/info/container_test.go @@ -0,0 +1,232 @@ +// 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{ + ContainerReference: ContainerReference{ + 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{ + ContainerReference: ContainerReference{ + 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 := 1; i < N+1; i++ { + data[i-1] = uint64(i) + } + percentages := []int{ + 80, + 90, + 50, + } + percentiles := uint64Slice(data).Percentiles(percentages...) + for _, s := range percentiles { + if s.Value != uint64(s.Percentage) { + t.Errorf("%v percentile data should be %v, but got %v", s.Percentage, s.Percentage, s.Value) + } + } +} + +func TestPercentilesSmallDataSet(t *testing.T) { + var value uint64 = 11 + data := []uint64{value} + + percentages := []int{ + 80, + 90, + 50, + } + percentiles := uint64Slice(data).Percentiles(percentages...) + for _, s := range percentiles { + if s.Value != value { + t.Errorf("%v percentile data should be %v, but got %v", s.Percentage, value, s.Value) + } + } +} + +func TestNewSampleNilStats(t *testing.T) { + stats := &ContainerStats{ + Cpu: &CpuStats{}, + Memory: &MemoryStats{}, + } + stats.Cpu.Usage.PerCpu = []uint64{uint64(10)} + stats.Cpu.Usage.Total = uint64(10) + stats.Cpu.Usage.System = uint64(2) + stats.Cpu.Usage.User = uint64(8) + stats.Memory.Usage = uint64(200) + + sample, err := NewSample(nil, stats) + if err == nil { + t.Errorf("generated an unexpected sample: %+v", sample) + } + + sample, err = NewSample(stats, nil) + if err == nil { + t.Errorf("generated an unexpected sample: %+v", sample) + } +} + +func createStats(cpuUsage, memUsage uint64, timestamp time.Time) *ContainerStats { + stats := &ContainerStats{ + Cpu: &CpuStats{}, + Memory: &MemoryStats{}, + } + stats.Cpu.Usage.PerCpu = []uint64{cpuUsage} + stats.Cpu.Usage.Total = cpuUsage + stats.Cpu.Usage.System = 0 + stats.Cpu.Usage.User = cpuUsage + stats.Memory.Usage = memUsage + stats.Timestamp = timestamp + return stats +} + +func TestAddSample(t *testing.T) { + cpuPrevUsage := uint64(10) + cpuCurrentUsage := uint64(15) + memCurrentUsage := uint64(200) + prevTime := time.Now() + + prev := createStats(cpuPrevUsage, memCurrentUsage, prevTime) + current := createStats(cpuCurrentUsage, memCurrentUsage, prevTime.Add(1*time.Second)) + + sample, err := NewSample(prev, current) + if err != nil { + t.Errorf("should be able to generate a sample. but received error: %v", err) + } + if sample == nil { + t.Fatalf("nil sample and nil error. unexpected result!") + } + + if sample.Memory.Usage != memCurrentUsage { + t.Errorf("wrong memory usage: %v. should be %v", sample.Memory.Usage, memCurrentUsage) + } + + if sample.Cpu.Usage != cpuCurrentUsage-cpuPrevUsage { + t.Errorf("wrong CPU usage: %v. should be %v", sample.Cpu.Usage, cpuCurrentUsage-cpuPrevUsage) + } +} + +func TestAddSampleIncompleteStats(t *testing.T) { + cpuPrevUsage := uint64(10) + cpuCurrentUsage := uint64(15) + memCurrentUsage := uint64(200) + prevTime := time.Now() + + prev := createStats(cpuPrevUsage, memCurrentUsage, prevTime) + current := createStats(cpuCurrentUsage, memCurrentUsage, prevTime.Add(1*time.Second)) + stats := &ContainerStats{ + Cpu: prev.Cpu, + Memory: nil, + } + sample, err := NewSample(stats, current) + if err == nil { + t.Errorf("generated an unexpected sample: %+v", sample) + } + sample, err = NewSample(prev, stats) + if err == nil { + t.Errorf("generated an unexpected sample: %+v", sample) + } + + stats = &ContainerStats{ + Cpu: nil, + Memory: prev.Memory, + } + sample, err = NewSample(stats, current) + if err == nil { + t.Errorf("generated an unexpected sample: %+v", sample) + } + sample, err = NewSample(prev, stats) + if err == nil { + t.Errorf("generated an unexpected sample: %+v", sample) + } +} + +func TestAddSampleWrongOrder(t *testing.T) { + cpuPrevUsage := uint64(10) + cpuCurrentUsage := uint64(15) + memCurrentUsage := uint64(200) + prevTime := time.Now() + + prev := createStats(cpuPrevUsage, memCurrentUsage, prevTime) + current := createStats(cpuCurrentUsage, memCurrentUsage, prevTime.Add(1*time.Second)) + + sample, err := NewSample(current, prev) + if err == nil { + t.Errorf("generated an unexpected sample: %+v", sample) + } +} + +func TestAddSampleWrongCpuUsage(t *testing.T) { + cpuPrevUsage := uint64(15) + cpuCurrentUsage := uint64(10) + memCurrentUsage := uint64(200) + prevTime := time.Now() + + prev := createStats(cpuPrevUsage, memCurrentUsage, prevTime) + current := createStats(cpuCurrentUsage, memCurrentUsage, prevTime.Add(1*time.Second)) + + sample, err := NewSample(prev, current) + if err == nil { + t.Errorf("generated an unexpected sample: %+v", sample) + } +} diff --git a/third_party/src/github.com/google/cadvisor/info/machine.go b/third_party/src/github.com/google/cadvisor/info/machine.go new file mode 100644 index 00000000000..7415dc9edf7 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/info/machine.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 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 VersionInfo struct { + // Kernel version. + KernelVersion string `json:"kernel_version"` + + // OS image being used for cadvisor container, or host image if running on host directly. + ContainerOsVersion string `json:"container_os_version"` + + // Docker version. + DockerVersion string `json:"docker_version"` + + // cAdvisor version. + CadvisorVersion string `json:"cadvisor_version"` +} + +type MachineInfoFactory interface { + GetMachineInfo() (*MachineInfo, error) + GetVersionInfo() (*VersionInfo, error) +} diff --git a/third_party/src/github.com/google/cadvisor/info/version.go b/third_party/src/github.com/google/cadvisor/info/version.go new file mode 100644 index 00000000000..a00a0222fe6 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/info/version.go @@ -0,0 +1,18 @@ +// 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 + +// Version of cAdvisor. +const VERSION = "0.1.0" diff --git a/third_party/src/github.com/google/cadvisor/logo.png b/third_party/src/github.com/google/cadvisor/logo.png new file mode 100644 index 00000000000..4d9258da109 Binary files /dev/null and b/third_party/src/github.com/google/cadvisor/logo.png differ diff --git a/third_party/src/github.com/google/cadvisor/manager/container.go b/third_party/src/github.com/google/cadvisor/manager/container.go new file mode 100644 index 00000000000..b366c9d6d84 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/manager/container.go @@ -0,0 +1,174 @@ +// 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 ( + "fmt" + "log" + "sync" + "time" + + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/storage" +) + +// Internal mirror of the external data structure. +type containerStat struct { + Timestamp time.Time + Data *info.ContainerStats +} +type containerInfo struct { + info.ContainerReference + Subcontainers []info.ContainerReference + Spec *info.ContainerSpec +} + +type containerData struct { + handler container.ContainerHandler + info containerInfo + storageDriver storage.StorageDriver + 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, driver storage.StorageDriver) (*containerData, error) { + if driver == nil { + return nil, fmt.Errorf("nil storage driver") + } + cont := &containerData{} + handler, err := container.NewContainerHandler(containerName) + if err != nil { + return nil, err + } + cont.handler = handler + ref, err := handler.ContainerReference() + if err != nil { + return nil, err + } + cont.info.Name = ref.Name + cont.info.Aliases = ref.Aliases + cont.storageDriver = driver + cont.stop = make(chan bool, 1) + + return cont, nil +} + +func (c *containerData) housekeeping() { + // Housekeep every second. + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + for { + select { + case <-c.stop: + // Stop housekeeping when signaled. + return + case <-ticker.C: + 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 + } + ref, err := c.handler.ContainerReference() + if err != nil { + return err + } + err = c.storageDriver.AddStats(ref, stats) + if err != nil { + return err + } + 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/third_party/src/github.com/google/cadvisor/manager/machine.go b/third_party/src/github.com/google/cadvisor/manager/machine.go new file mode 100644 index 00000000000..3ca4fd2fa85 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/manager/machine.go @@ -0,0 +1,127 @@ +// 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 ( + "bytes" + "fmt" + "io/ioutil" + "regexp" + "strconv" + "strings" + "syscall" + + dclient "github.com/fsouza/go-dockerclient" + "github.com/google/cadvisor/container/docker" + "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 +} + +func getVersionInfo() (*info.VersionInfo, error) { + + kernel_version := getKernelVersion() + container_os := getContainerOsVersion() + docker_version := getDockerVersion() + + return &info.VersionInfo{ + KernelVersion: kernel_version, + ContainerOsVersion: container_os, + DockerVersion: docker_version, + CadvisorVersion: info.VERSION, + }, nil +} + +func getContainerOsVersion() string { + container_os := "Unknown" + os_release, err := ioutil.ReadFile("/etc/os-release") + if err == nil { + // We might be running in a busybox or some hand-crafted image. + // It's useful to know why cadvisor didn't come up. + for _, line := range strings.Split(string(os_release), "\n") { + parsed := strings.Split(line, "\"") + if len(parsed) == 3 && parsed[0] == "PRETTY_NAME=" { + container_os = parsed[1] + break + } + } + } + return container_os +} + +func getDockerVersion() string { + docker_version := "Unknown" + client, err := dclient.NewClient(*docker.ArgDockerEndpoint) + if err == nil { + version, err := client.Version() + if err == nil { + docker_version = version.Get("Version") + } + } + return docker_version +} + +func getKernelVersion() string { + uname := &syscall.Utsname{} + + if err := syscall.Uname(uname); err != nil { + return "Unknown" + } + + release := make([]byte, len(uname.Release)) + i := 0 + for _, c := range uname.Release { + release[i] = byte(c) + i++ + } + release = release[:bytes.IndexByte(release, 0)] + + return string(release) +} diff --git a/third_party/src/github.com/google/cadvisor/manager/manager.go b/third_party/src/github.com/google/cadvisor/manager/manager.go new file mode 100644 index 00000000000..1b0503551c4 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/manager/manager.go @@ -0,0 +1,300 @@ +// 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" + "github.com/google/cadvisor/storage" +) + +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) + + // Get version information about different components we depend on. + GetVersionInfo() (*info.VersionInfo, error) +} + +func New(driver storage.StorageDriver) (Manager, error) { + if driver == nil { + return nil, fmt.Errorf("nil storage driver!") + } + 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) + + versionInfo, err := getVersionInfo() + if err != nil { + return nil, err + } + newManager.versionInfo = *versionInfo + log.Printf("Version: %+v", newManager.versionInfo) + newManager.storageDriver = driver + + return newManager, nil +} + +type manager struct { + containers map[string]*containerData + containersLock sync.RWMutex + storageDriver storage.StorageDriver + machineInfo info.MachineInfo + versionInfo info.VersionInfo +} + +// 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 + } + + var percentiles *info.ContainerStatsPercentiles + var samples []*info.ContainerStatsSample + var stats []*info.ContainerStats + // TODO(monnand): These numbers should not be hard coded + percentiles, err = m.storageDriver.Percentiles( + cinfo.Name, + []int{50, 80, 90, 99}, + []int{50, 80, 90, 99}, + ) + if err != nil { + return nil, err + } + samples, err = m.storageDriver.Samples(cinfo.Name, 1024) + if err != nil { + return nil, err + } + + stats, err = m.storageDriver.RecentStats(cinfo.Name, 1024) + if err != nil { + return nil, err + } + + // Make a copy of the info for the user. + ret := &info.ContainerInfo{ + ContainerReference: info.ContainerReference{ + Name: cinfo.Name, + Aliases: cinfo.Aliases, + }, + Subcontainers: cinfo.Subcontainers, + Spec: cinfo.Spec, + StatsPercentiles: percentiles, + Samples: samples, + Stats: stats, + } + + // 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) + } + } + return ret, nil +} + +func (m *manager) GetMachineInfo() (*info.MachineInfo, error) { + // Copy and return the MachineInfo. + ret := m.machineInfo + return &ret, nil +} + +func (m *manager) GetVersionInfo() (*info.VersionInfo, error) { + ret := m.versionInfo + 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, m.storageDriver) + if err != nil { + return nil, err + } + + // Add to the containers map. + func() { + m.containersLock.Lock() + defer m.containersLock.Unlock() + + // Add the container name and all its aliases. + m.containers[containerName] = cont + for _, alias := range cont.info.Aliases { + m.containers[alias] = cont + } + }() + log.Printf("Added container: %s (aliases: %s)", containerName, cont.info.Aliases) + + // 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 (and all its aliases). + delete(m.containers, containerName) + for _, alias := range cont.info.Aliases { + delete(m.containers, alias) + } + log.Printf("Destroyed container: %s (aliases: %s)", containerName, cont.info.Aliases) + return nil +} + +// Detect all containers that have been added or deleted. +func (m *manager) getContainersDiff() (added []info.ContainerReference, removed []info.ContainerReference, 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, info.ContainerReference{Name: "/"}) + + // Determine which were added and which were removed. + allContainersSet := make(map[string]*containerData) + for name, d := range m.containers { + // Only add the canonical name. + if d.info.Name == name { + allContainersSet[name] = d + } + } + for _, c := range allContainers { + delete(allContainersSet, c.Name) + _, ok := m.containers[c.Name] + if !ok { + added = append(added, c) + } + } + + // Removed ones are no longer in the container listing. + for _, d := range allContainersSet { + removed = append(removed, d.info.ContainerReference) + } + + 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 _, container := range added { + _, err = m.createContainer(container.Name) + if err != nil { + return fmt.Errorf("Failed to create existing container: %s: %s", container.Name, err) + } + } + + // Remove the old containers. + for _, container := range removed { + err = m.destroyContainer(container.Name) + if err != nil { + return fmt.Errorf("Failed to destroy existing container: %s: %s", container.Name, err) + } + } + + return nil +} diff --git a/third_party/src/github.com/google/cadvisor/pages/containers.go b/third_party/src/github.com/google/cadvisor/pages/containers.go new file mode 100644 index 00000000000..58605a8bc8e --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/pages/containers.go @@ -0,0 +1,222 @@ +// 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, + "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 []info.ContainerReference + Subcontainers []info.ContainerReference + 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(container info.ContainerReference, basenameOnly bool, cssClasses string) interface{} { + var displayName string + containerName := container.Name + if len(container.Aliases) > 0 { + displayName = container.Aliases[0] + } else if basenameOnly { + displayName = path.Base(string(container.Name)) + } else { + displayName = string(container.Name) + } + if container.Name == "root" { + containerName = "/" + } else if strings.Contains(container.Name, " ") { + // If it has a space, it is an a.k.a, so keep the base-name + containerName = container.Name[:strings.Index(container.Name, " ")] + } + return template.HTML(fmt.Sprintf("%s", cssClasses, ContainersPage[:len(ContainersPage)-1], containerName, displayName)) +} + +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, machine *info.MachineInfo) int { + // Saturate limit to the machine size. + limit := uint64(spec.Memory.Limit) + if limit > uint64(machine.MemoryCapacity) { + limit = uint64(machine.MemoryCapacity) + } + + return int((usage * 100) / 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, machine *info.MachineInfo) int { + return toMemoryPercent((stats[len(stats)-1].Memory.Usage), spec, machine) +} + +func getHotMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { + return toMemoryPercent((stats[len(stats)-1].Memory.WorkingSet), spec, machine) +} + +func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { + latestStats := stats[len(stats)-1].Memory + return toMemoryPercent((latestStats.Usage)-(latestStats.WorkingSet), spec, machine) +} + +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 []info.ContainerReference + parentContainers = append(parentContainers, info.ContainerReference{Name: "root"}) + parentName := "" + for _, part := range strings.Split(string(cont.Name), "/") { + if part == "" { + continue + } + parentName += "/" + part + parentContainers = append(parentContainers, info.ContainerReference{Name: parentName}) + } + + // Pick the shortest name of the container as the display name. + displayName := cont.Name + for _, alias := range cont.Aliases { + if len(displayName) >= len(alias) { + displayName = alias + } + } + + // Replace the last part of the parent containers with the displayName. + if displayName != cont.Name { + parentContainers[len(parentContainers)-1] = info.ContainerReference{ + Name: fmt.Sprintf("%s (%s)", displayName, path.Base(cont.Name)), + } + } + + data := &pageData{ + ContainerName: displayName, + // TODO(vmarmol): Only use strings for this. + 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/third_party/src/github.com/google/cadvisor/pages/containers_html.go b/third_party/src/github.com/google/cadvisor/pages/containers_html.go new file mode 100644 index 00000000000..037aa3f4da6 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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 .MachineInfo}}%) +
+
+

Page Faults

+
+
+
+ {{end}} +
+ {{end}} +
+ + + +` diff --git a/third_party/src/github.com/google/cadvisor/pages/static/containers_css.go b/third_party/src/github.com/google/cadvisor/pages/static/containers_css.go new file mode 100644 index 00000000000..dec45c54f13 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/pages/static/containers_js.go b/third_party/src/github.com/google/cadvisor/pages/static/containers_js.go new file mode 100644 index 00000000000..b907b344d65 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/pages/static/containers_js.go @@ -0,0 +1,295 @@ +// 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}; + opts.legend = {position: 'bottom'}; + 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, machineInfo, 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) { + // Saturate to the machine size. + var limit = containerInfo.spec.memory.limit; + if (limit > machineInfo.memory_capacity) { + limit = machineInfo.memory_capacity; + } + + memoryUsage = Math.round((cur.memory.usage / 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", machineInfo, 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/third_party/src/github.com/google/cadvisor/pages/static/static.go b/third_party/src/github.com/google/cadvisor/pages/static/static.go new file mode 100644 index 00000000000..4d3551ff0f8 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/sampling/autofilter.go b/third_party/src/github.com/google/cadvisor/sampling/autofilter.go new file mode 100644 index 00000000000..7389de52d0e --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/sampling/autoreset.go b/third_party/src/github.com/google/cadvisor/sampling/autoreset.go new file mode 100644 index 00000000000..b466e5e6b9b --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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 NewPeriodicallyResetSampler(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/third_party/src/github.com/google/cadvisor/sampling/chainsample.go b/third_party/src/github.com/google/cadvisor/sampling/chainsample.go new file mode 100644 index 00000000000..a6d94288cdb --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/sampling/chainsample.go @@ -0,0 +1,171 @@ +// 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 ( + "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 observation 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/third_party/src/github.com/google/cadvisor/sampling/chainsample_test.go b/third_party/src/github.com/google/cadvisor/sampling/chainsample_test.go new file mode 100644 index 00000000000..85d6e3e7e2d --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/sampling/chainsample_test.go @@ -0,0 +1,43 @@ +// 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 "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/third_party/src/github.com/google/cadvisor/sampling/doc.go b/third_party/src/github.com/google/cadvisor/sampling/doc.go new file mode 100644 index 00000000000..8eeb6c47d63 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/sampling/es.go b/third_party/src/github.com/google/cadvisor/sampling/es.go new file mode 100644 index 00000000000..64f40be1918 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/sampling/es_test.go b/third_party/src/github.com/google/cadvisor/sampling/es_test.go new file mode 100644 index 00000000000..d45b27628b5 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/sampling/reservoir.go b/third_party/src/github.com/google/cadvisor/sampling/reservoir.go new file mode 100644 index 00000000000..a0276360e89 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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, numInstances). + // 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/third_party/src/github.com/google/cadvisor/sampling/reservoir_test.go b/third_party/src/github.com/google/cadvisor/sampling/reservoir_test.go new file mode 100644 index 00000000000..1113b363c6a --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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/third_party/src/github.com/google/cadvisor/sampling/sampler.go b/third_party/src/github.com/google/cadvisor/sampling/sampler.go new file mode 100644 index 00000000000..549f6e9e4d1 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/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 observation will be sampled after removing some elements is + // implementation defined. + Filter(filter func(interface{}) bool) +} diff --git a/third_party/src/github.com/google/cadvisor/storage/memory/memory.go b/third_party/src/github.com/google/cadvisor/storage/memory/memory.go new file mode 100644 index 00000000000..dd353cc4634 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/storage/memory/memory.go @@ -0,0 +1,224 @@ +// 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 memory + +import ( + "container/list" + "fmt" + "sync" + + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/sampling" + "github.com/google/cadvisor/storage" +) + +// containerStorage is used to store per-container information +type containerStorage struct { + ref info.ContainerReference + prevStats *info.ContainerStats + sampler sampling.Sampler + recentStats *list.List + maxNumStats int + maxMemUsage uint64 + lock sync.RWMutex +} + +func (self *containerStorage) updatePrevStats(stats *info.ContainerStats) { + if stats == nil || stats.Cpu == nil || stats.Memory == nil { + // discard incomplete stats + self.prevStats = nil + return + } + if self.prevStats == nil { + self.prevStats = &info.ContainerStats{ + Cpu: &info.CpuStats{}, + Memory: &info.MemoryStats{}, + } + } + // make a deep copy. + self.prevStats.Timestamp = stats.Timestamp + *self.prevStats.Cpu = *stats.Cpu + self.prevStats.Cpu.Usage.PerCpu = make([]uint64, len(stats.Cpu.Usage.PerCpu)) + for i, perCpu := range stats.Cpu.Usage.PerCpu { + self.prevStats.Cpu.Usage.PerCpu[i] = perCpu + } + *self.prevStats.Memory = *stats.Memory +} + +func (self *containerStorage) AddStats(stats *info.ContainerStats) error { + self.lock.Lock() + defer self.lock.Unlock() + if self.prevStats != nil { + sample, err := info.NewSample(self.prevStats, stats) + if err != nil { + return fmt.Errorf("wrong stats: %v", err) + } + if sample != nil { + self.sampler.Update(sample) + } + } + if stats.Memory != nil { + if self.maxMemUsage < stats.Memory.Usage { + self.maxMemUsage = stats.Memory.Usage + } + } + if self.recentStats.Len() >= self.maxNumStats { + self.recentStats.Remove(self.recentStats.Front()) + } + self.recentStats.PushBack(stats) + self.updatePrevStats(stats) + return nil +} + +func (self *containerStorage) RecentStats(numStats int) ([]*info.ContainerStats, error) { + self.lock.RLock() + defer self.lock.RUnlock() + if self.recentStats.Len() < numStats || numStats < 0 { + numStats = self.recentStats.Len() + } + ret := make([]*info.ContainerStats, 0, numStats) + e := self.recentStats.Front() + for i := 0; i < numStats; i++ { + data, ok := e.Value.(*info.ContainerStats) + if !ok { + return nil, fmt.Errorf("The %vth element is not a ContainerStats", i) + } + ret = append(ret, data) + e = e.Next() + if e == nil { + break + } + } + return ret, nil +} + +func (self *containerStorage) Samples(numSamples int) ([]*info.ContainerStatsSample, error) { + self.lock.RLock() + defer self.lock.RUnlock() + if self.sampler.Len() < numSamples || numSamples < 0 { + numSamples = self.sampler.Len() + } + ret := make([]*info.ContainerStatsSample, 0, numSamples) + + var err error + self.sampler.Map(func(d interface{}) { + if len(ret) >= numSamples || err != nil { + return + } + sample, ok := d.(*info.ContainerStatsSample) + if !ok { + err = fmt.Errorf("An element in the sample is not a ContainerStatsSample") + } + ret = append(ret, sample) + }) + if err != nil { + return nil, err + } + return ret, nil +} + +func (self *containerStorage) Percentiles(cpuPercentiles, memPercentiles []int) (*info.ContainerStatsPercentiles, error) { + samples, err := self.Samples(-1) + if err != nil { + return nil, err + } + if len(samples) == 0 { + return nil, nil + } + ret := info.NewPercentiles(samples, cpuPercentiles, memPercentiles) + ret.MaxMemoryUsage = self.maxMemUsage + return ret, nil +} + +func newContainerStore(ref info.ContainerReference, maxNumSamples, maxNumStats int) *containerStorage { + s := sampling.NewReservoirSampler(maxNumSamples) + return &containerStorage{ + ref: ref, + recentStats: list.New(), + sampler: s, + maxNumStats: maxNumStats, + } +} + +type InMemoryStorage struct { + lock sync.RWMutex + containerStorageMap map[string]*containerStorage + maxNumSamples int + maxNumStats int +} + +func (self *InMemoryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { + var cstore *containerStorage + var ok bool + self.lock.Lock() + if cstore, ok = self.containerStorageMap[ref.Name]; !ok { + cstore = newContainerStore(ref, self.maxNumSamples, self.maxNumStats) + self.containerStorageMap[ref.Name] = cstore + } + self.lock.Unlock() + return cstore.AddStats(stats) +} + +func (self *InMemoryStorage) Samples(name string, numSamples int) ([]*info.ContainerStatsSample, error) { + var cstore *containerStorage + var ok bool + self.lock.RLock() + if cstore, ok = self.containerStorageMap[name]; !ok { + return nil, fmt.Errorf("unable to find data for container %v", name) + } + self.lock.RUnlock() + + return cstore.Samples(numSamples) +} + +func (self *InMemoryStorage) RecentStats(name string, numStats int) ([]*info.ContainerStats, error) { + var cstore *containerStorage + var ok bool + self.lock.RLock() + if cstore, ok = self.containerStorageMap[name]; !ok { + return nil, fmt.Errorf("unable to find data for container %v", name) + } + self.lock.RUnlock() + + return cstore.RecentStats(numStats) +} + +func (self *InMemoryStorage) Percentiles(name string, cpuPercentiles, memPercentiles []int) (*info.ContainerStatsPercentiles, error) { + var cstore *containerStorage + var ok bool + self.lock.RLock() + if cstore, ok = self.containerStorageMap[name]; !ok { + return nil, fmt.Errorf("unable to find data for container %v", name) + } + self.lock.RUnlock() + + return cstore.Percentiles(cpuPercentiles, memPercentiles) +} + +func (self *InMemoryStorage) Close() error { + self.lock.Lock() + self.containerStorageMap = make(map[string]*containerStorage, 32) + self.lock.Unlock() + return nil +} + +func New(maxNumSamples, maxNumStats int) storage.StorageDriver { + ret := &InMemoryStorage{ + containerStorageMap: make(map[string]*containerStorage, 32), + maxNumSamples: maxNumSamples, + maxNumStats: maxNumStats, + } + return ret +} diff --git a/third_party/src/github.com/google/cadvisor/storage/memory/memory_test.go b/third_party/src/github.com/google/cadvisor/storage/memory/memory_test.go new file mode 100644 index 00000000000..98f9a6a8810 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/storage/memory/memory_test.go @@ -0,0 +1,49 @@ +// 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 memory + +import ( + "testing" + + "github.com/google/cadvisor/storage" + "github.com/google/cadvisor/storage/test" +) + +func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) { + maxSize := 200 + + var driver storage.StorageDriver + for N := 10; N < maxSize; N += 10 { + driver = New(N, N) + f(driver, t) + } + +} + +func TestMaxMemoryUsage(t *testing.T) { + runStorageTest(test.StorageDriverTestMaxMemoryUsage, t) +} + +func TestSampleCpuUsage(t *testing.T) { + runStorageTest(test.StorageDriverTestSampleCpuUsage, t) +} + +func TestSamplesWithoutSample(t *testing.T) { + runStorageTest(test.StorageDriverTestSamplesWithoutSample, t) +} + +func TestPercentilessWithoutSample(t *testing.T) { + runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t) +} diff --git a/third_party/src/github.com/google/cadvisor/storage/storage.go b/third_party/src/github.com/google/cadvisor/storage/storage.go new file mode 100644 index 00000000000..51f0b9c00f5 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/storage/storage.go @@ -0,0 +1,40 @@ +// 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 storage + +import "github.com/google/cadvisor/info" + +type StorageDriver interface { + AddStats(ref info.ContainerReference, stats *info.ContainerStats) error + + // Read most recent stats. numStats indicates max number of stats + // returned. The returned stats must be consecutive observed stats. If + // numStats < 0, then return all stats stored in the storage. + RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) + + // Read the specified percentiles of CPU and memory usage of the container. + // The implementation decides which time range to look at. + Percentiles(containerName string, cpuUsagePercentiles []int, memUsagePercentiles []int) (*info.ContainerStatsPercentiles, error) + + // Returns samples of the container stats. If numSamples < 0, then + // the number of returned samples is implementation defined. Otherwise, the driver + // should return at most numSamples samples. + Samples(containername string, numSamples int) ([]*info.ContainerStatsSample, error) + + // Close will clear the state of the storage driver. The elements + // stored in the underlying storage may or may not be deleted depending + // on the implementation of the storage driver. + Close() error +} diff --git a/third_party/src/github.com/google/cadvisor/storage/test/storagetests.go b/third_party/src/github.com/google/cadvisor/storage/test/storagetests.go new file mode 100644 index 00000000000..e0f27f76aa7 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/storage/test/storagetests.go @@ -0,0 +1,170 @@ +// 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 test + +import ( + "math/rand" + "testing" + "time" + + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/storage" +) + +func buildTrace(cpu, mem []uint64, duration time.Duration) []*info.ContainerStats { + if len(cpu) != len(mem) { + panic("len(cpu) != len(mem)") + } + + ret := make([]*info.ContainerStats, len(cpu)) + currentTime := time.Now() + + var cpuTotalUsage uint64 = 0 + for i, cpuUsage := range cpu { + cpuTotalUsage += cpuUsage + stats := new(info.ContainerStats) + stats.Cpu = new(info.CpuStats) + stats.Memory = new(info.MemoryStats) + stats.Timestamp = currentTime + currentTime = currentTime.Add(duration) + + stats.Cpu.Usage.Total = cpuTotalUsage + stats.Cpu.Usage.User = stats.Cpu.Usage.Total + stats.Cpu.Usage.System = 0 + + stats.Memory.Usage = mem[i] + + ret[i] = stats + } + return ret +} + +// The underlying driver must be able to hold more than 10 samples. +func StorageDriverTestSampleCpuUsage(driver storage.StorageDriver, t *testing.T) { + defer driver.Close() + N := 10 + cpuTrace := make([]uint64, 0, N) + memTrace := make([]uint64, 0, N) + + // We need N+1 observations to get N samples + for i := 0; i < N+1; i++ { + cpuTrace = append(cpuTrace, uint64(rand.Intn(1000))) + memTrace = append(memTrace, uint64(rand.Intn(1000))) + } + + samplePeriod := 1 * time.Second + + ref := info.ContainerReference{ + Name: "container", + } + + trace := buildTrace(cpuTrace, memTrace, samplePeriod) + + for _, stats := range trace { + driver.AddStats(ref, stats) + } + + samples, err := driver.Samples(ref.Name, N) + if err != nil { + t.Errorf("unable to sample stats: %v", err) + } + for _, sample := range samples { + if sample.Duration != samplePeriod { + t.Errorf("sample duration is %v, not %v", sample.Duration, samplePeriod) + } + cpuUsage := sample.Cpu.Usage + found := false + for _, u := range cpuTrace { + if u == cpuUsage { + found = true + } + } + if !found { + t.Errorf("unable to find cpu usage %v", cpuUsage) + } + } +} + +func StorageDriverTestMaxMemoryUsage(driver storage.StorageDriver, t *testing.T) { + defer driver.Close() + N := 100 + memTrace := make([]uint64, N) + cpuTrace := make([]uint64, N) + for i := 0; i < N; i++ { + memTrace[i] = uint64(i + 1) + cpuTrace[i] = uint64(1) + } + + ref := info.ContainerReference{ + Name: "container", + } + + trace := buildTrace(cpuTrace, memTrace, 1*time.Second) + + for _, stats := range trace { + driver.AddStats(ref, stats) + } + + percentiles, err := driver.Percentiles(ref.Name, []int{50}, []int{50}) + if err != nil { + t.Errorf("unable to call Percentiles(): %v", err) + } + maxUsage := uint64(N) + if percentiles.MaxMemoryUsage != maxUsage { + t.Fatalf("Max memory usage should be %v; received %v", maxUsage, percentiles.MaxMemoryUsage) + } +} + +func StorageDriverTestSamplesWithoutSample(driver storage.StorageDriver, t *testing.T) { + defer driver.Close() + trace := buildTrace( + []uint64{10}, + []uint64{10}, + 1*time.Second) + ref := info.ContainerReference{ + Name: "container", + } + driver.AddStats(ref, trace[0]) + samples, err := driver.Samples(ref.Name, -1) + if err != nil { + t.Fatal(err) + } + if len(samples) != 0 { + t.Errorf("There should be no sample") + } +} + +func StorageDriverTestPercentilesWithoutSample(driver storage.StorageDriver, t *testing.T) { + defer driver.Close() + trace := buildTrace( + []uint64{10}, + []uint64{10}, + 1*time.Second) + ref := info.ContainerReference{ + Name: "container", + } + driver.AddStats(ref, trace[0]) + percentiles, err := driver.Percentiles( + ref.Name, + []int{50}, + []int{50}, + ) + if err != nil { + t.Fatal(err) + } + if percentiles != nil { + t.Errorf("There should be no percentiles") + } +} diff --git a/third_party/src/github.com/stretchr/objx/LICENSE.md b/third_party/src/github.com/stretchr/objx/LICENSE.md new file mode 100644 index 00000000000..2199945813c --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/LICENSE.md @@ -0,0 +1,23 @@ +objx - by Mat Ryer and Tyler Bunnell + +The MIT License (MIT) + +Copyright (c) 2014 Stretchr, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/src/github.com/stretchr/objx/README.md b/third_party/src/github.com/stretchr/objx/README.md new file mode 100644 index 00000000000..4aa180687a7 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/README.md @@ -0,0 +1,3 @@ +# objx + + * Jump into the [API Documentation](http://godoc.org/github.com/stretchr/objx) diff --git a/third_party/src/github.com/stretchr/objx/accessors.go b/third_party/src/github.com/stretchr/objx/accessors.go new file mode 100644 index 00000000000..721bcac7993 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/accessors.go @@ -0,0 +1,179 @@ +package objx + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// arrayAccesRegexString is the regex used to extract the array number +// from the access path +const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$` + +// arrayAccesRegex is the compiled arrayAccesRegexString +var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString) + +// Get gets the value using the specified selector and +// returns it inside a new Obj object. +// +// If it cannot find the value, Get will return a nil +// value inside an instance of Obj. +// +// Get can only operate directly on map[string]interface{} and []interface. +// +// Example +// +// To access the title of the third chapter of the second book, do: +// +// o.Get("books[1].chapters[2].title") +func (m Map) Get(selector string) *Value { + rawObj := access(m, selector, nil, false, false) + return &Value{data: rawObj} +} + +// Set sets the value using the specified selector and +// returns the object on which Set was called. +// +// Set can only operate directly on map[string]interface{} and []interface +// +// Example +// +// To set the title of the third chapter of the second book, do: +// +// o.Set("books[1].chapters[2].title","Time to Go") +func (m Map) Set(selector string, value interface{}) Map { + access(m, selector, value, true, false) + return m +} + +// access accesses the object using the selector and performs the +// appropriate action. +func access(current, selector, value interface{}, isSet, panics bool) interface{} { + + switch selector.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + + if array, ok := current.([]interface{}); ok { + index := intFromInterface(selector) + + if index >= len(array) { + if panics { + panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) + } + return nil + } + + return array[index] + } + + return nil + + case string: + + selStr := selector.(string) + selSegs := strings.SplitN(selStr, PathSeparator, 2) + thisSel := selSegs[0] + index := -1 + var err error + + // https://github.com/stretchr/objx/issues/12 + if strings.Contains(thisSel, "[") { + + arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel) + + if len(arrayMatches) > 0 { + + // Get the key into the map + thisSel = arrayMatches[1] + + // Get the index into the array at the key + index, err = strconv.Atoi(arrayMatches[2]) + + if err != nil { + // This should never happen. If it does, something has gone + // seriously wrong. Panic. + panic("objx: Array index is not an integer. Must use array[int].") + } + + } + } + + if curMap, ok := current.(Map); ok { + current = map[string]interface{}(curMap) + } + + // get the object in question + switch current.(type) { + case map[string]interface{}: + curMSI := current.(map[string]interface{}) + if len(selSegs) <= 1 && isSet { + curMSI[thisSel] = value + return nil + } else { + current = curMSI[thisSel] + } + default: + current = nil + } + + if current == nil && panics { + panic(fmt.Sprintf("objx: '%v' invalid on object.", selector)) + } + + // do we need to access the item of an array? + if index > -1 { + if array, ok := current.([]interface{}); ok { + if index < len(array) { + current = array[index] + } else { + if panics { + panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) + } + current = nil + } + } + } + + if len(selSegs) > 1 { + current = access(current, selSegs[1], value, isSet, panics) + } + + } + + return current + +} + +// intFromInterface converts an interface object to the largest +// representation of an unsigned integer using a type switch and +// assertions +func intFromInterface(selector interface{}) int { + var value int + switch selector.(type) { + case int: + value = selector.(int) + case int8: + value = int(selector.(int8)) + case int16: + value = int(selector.(int16)) + case int32: + value = int(selector.(int32)) + case int64: + value = int(selector.(int64)) + case uint: + value = int(selector.(uint)) + case uint8: + value = int(selector.(uint8)) + case uint16: + value = int(selector.(uint16)) + case uint32: + value = int(selector.(uint32)) + case uint64: + value = int(selector.(uint64)) + default: + panic("objx: array access argument is not an integer type (this should never happen)") + } + + return value +} diff --git a/third_party/src/github.com/stretchr/objx/accessors_test.go b/third_party/src/github.com/stretchr/objx/accessors_test.go new file mode 100644 index 00000000000..ce5d8e4aa1a --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/accessors_test.go @@ -0,0 +1,145 @@ +package objx + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAccessorsAccessGetSingleField(t *testing.T) { + + current := map[string]interface{}{"name": "Tyler"} + assert.Equal(t, "Tyler", access(current, "name", nil, false, true)) + +} +func TestAccessorsAccessGetDeep(t *testing.T) { + + current := map[string]interface{}{"name": map[string]interface{}{"first": "Tyler", "last": "Bunnell"}} + assert.Equal(t, "Tyler", access(current, "name.first", nil, false, true)) + assert.Equal(t, "Bunnell", access(current, "name.last", nil, false, true)) + +} +func TestAccessorsAccessGetDeepDeep(t *testing.T) { + + current := map[string]interface{}{"one": map[string]interface{}{"two": map[string]interface{}{"three": map[string]interface{}{"four": 4}}}} + assert.Equal(t, 4, access(current, "one.two.three.four", nil, false, true)) + +} +func TestAccessorsAccessGetInsideArray(t *testing.T) { + + current := map[string]interface{}{"names": []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}} + assert.Equal(t, "Tyler", access(current, "names[0].first", nil, false, true)) + assert.Equal(t, "Bunnell", access(current, "names[0].last", nil, false, true)) + assert.Equal(t, "Capitol", access(current, "names[1].first", nil, false, true)) + assert.Equal(t, "Bollocks", access(current, "names[1].last", nil, false, true)) + + assert.Panics(t, func() { + access(current, "names[2]", nil, false, true) + }) + assert.Nil(t, access(current, "names[2]", nil, false, false)) + +} + +func TestAccessorsAccessGetFromArrayWithInt(t *testing.T) { + + current := []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}} + one := access(current, 0, nil, false, false) + two := access(current, 1, nil, false, false) + three := access(current, 2, nil, false, false) + + assert.Equal(t, "Tyler", one.(map[string]interface{})["first"]) + assert.Equal(t, "Capitol", two.(map[string]interface{})["first"]) + assert.Nil(t, three) + +} + +func TestAccessorsGet(t *testing.T) { + + current := New(map[string]interface{}{"name": "Tyler"}) + assert.Equal(t, "Tyler", current.Get("name").data) + +} + +func TestAccessorsAccessSetSingleField(t *testing.T) { + + current := map[string]interface{}{"name": "Tyler"} + access(current, "name", "Mat", true, false) + assert.Equal(t, current["name"], "Mat") + + access(current, "age", 29, true, true) + assert.Equal(t, current["age"], 29) + +} + +func TestAccessorsAccessSetSingleFieldNotExisting(t *testing.T) { + + current := map[string]interface{}{} + access(current, "name", "Mat", true, false) + assert.Equal(t, current["name"], "Mat") + +} + +func TestAccessorsAccessSetDeep(t *testing.T) { + + current := map[string]interface{}{"name": map[string]interface{}{"first": "Tyler", "last": "Bunnell"}} + + access(current, "name.first", "Mat", true, true) + access(current, "name.last", "Ryer", true, true) + + assert.Equal(t, "Mat", access(current, "name.first", nil, false, true)) + assert.Equal(t, "Ryer", access(current, "name.last", nil, false, true)) + +} +func TestAccessorsAccessSetDeepDeep(t *testing.T) { + + current := map[string]interface{}{"one": map[string]interface{}{"two": map[string]interface{}{"three": map[string]interface{}{"four": 4}}}} + + access(current, "one.two.three.four", 5, true, true) + + assert.Equal(t, 5, access(current, "one.two.three.four", nil, false, true)) + +} +func TestAccessorsAccessSetArray(t *testing.T) { + + current := map[string]interface{}{"names": []interface{}{"Tyler"}} + + access(current, "names[0]", "Mat", true, true) + + assert.Equal(t, "Mat", access(current, "names[0]", nil, false, true)) + +} +func TestAccessorsAccessSetInsideArray(t *testing.T) { + + current := map[string]interface{}{"names": []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}} + + access(current, "names[0].first", "Mat", true, true) + access(current, "names[0].last", "Ryer", true, true) + access(current, "names[1].first", "Captain", true, true) + access(current, "names[1].last", "Underpants", true, true) + + assert.Equal(t, "Mat", access(current, "names[0].first", nil, false, true)) + assert.Equal(t, "Ryer", access(current, "names[0].last", nil, false, true)) + assert.Equal(t, "Captain", access(current, "names[1].first", nil, false, true)) + assert.Equal(t, "Underpants", access(current, "names[1].last", nil, false, true)) + +} + +func TestAccessorsAccessSetFromArrayWithInt(t *testing.T) { + + current := []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}} + one := access(current, 0, nil, false, false) + two := access(current, 1, nil, false, false) + three := access(current, 2, nil, false, false) + + assert.Equal(t, "Tyler", one.(map[string]interface{})["first"]) + assert.Equal(t, "Capitol", two.(map[string]interface{})["first"]) + assert.Nil(t, three) + +} + +func TestAccessorsSet(t *testing.T) { + + current := New(map[string]interface{}{"name": "Tyler"}) + current.Set("name", "Mat") + assert.Equal(t, "Mat", current.Get("name").data) + +} diff --git a/third_party/src/github.com/stretchr/objx/codegen/array-access.txt b/third_party/src/github.com/stretchr/objx/codegen/array-access.txt new file mode 100644 index 00000000000..30602347512 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/codegen/array-access.txt @@ -0,0 +1,14 @@ + case []{1}: + a := object.([]{1}) + if isSet { + a[index] = value.({1}) + } else { + if index >= len(a) { + if panics { + panic(fmt.Sprintf("objx: Index %d is out of range because the []{1} only contains %d items.", index, len(a))) + } + return nil + } else { + return a[index] + } + } diff --git a/third_party/src/github.com/stretchr/objx/codegen/index.html b/third_party/src/github.com/stretchr/objx/codegen/index.html new file mode 100644 index 00000000000..379ffc3c0e4 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/codegen/index.html @@ -0,0 +1,86 @@ + + + + Codegen + + + + + +

+ Template +

+

+ Use {x} as a placeholder for each argument. +

+ + +

+ Arguments (comma separated) +

+

+ One block per line +

+ + +

+ Output +

+ + + + + + + + diff --git a/third_party/src/github.com/stretchr/objx/codegen/template.txt b/third_party/src/github.com/stretchr/objx/codegen/template.txt new file mode 100644 index 00000000000..b396900b8af --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/codegen/template.txt @@ -0,0 +1,286 @@ +/* + {4} ({1} and []{1}) + -------------------------------------------------- +*/ + +// {4} gets the value as a {1}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) {4}(optionalDefault ...{1}) {1} { + if s, ok := v.data.({1}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return {3} +} + +// Must{4} gets the value as a {1}. +// +// Panics if the object is not a {1}. +func (v *Value) Must{4}() {1} { + return v.data.({1}) +} + +// {4}Slice gets the value as a []{1}, returns the optionalDefault +// value or nil if the value is not a []{1}. +func (v *Value) {4}Slice(optionalDefault ...[]{1}) []{1} { + if s, ok := v.data.([]{1}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// Must{4}Slice gets the value as a []{1}. +// +// Panics if the object is not a []{1}. +func (v *Value) Must{4}Slice() []{1} { + return v.data.([]{1}) +} + +// Is{4} gets whether the object contained is a {1} or not. +func (v *Value) Is{4}() bool { + _, ok := v.data.({1}) + return ok +} + +// Is{4}Slice gets whether the object contained is a []{1} or not. +func (v *Value) Is{4}Slice() bool { + _, ok := v.data.([]{1}) + return ok +} + +// Each{4} calls the specified callback for each object +// in the []{1}. +// +// Panics if the object is the wrong type. +func (v *Value) Each{4}(callback func(int, {1}) bool) *Value { + + for index, val := range v.Must{4}Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// Where{4} uses the specified decider function to select items +// from the []{1}. The object contained in the result will contain +// only the selected items. +func (v *Value) Where{4}(decider func(int, {1}) bool) *Value { + + var selected []{1} + + v.Each{4}(func(index int, val {1}) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data:selected} + +} + +// Group{4} uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]{1}. +func (v *Value) Group{4}(grouper func(int, {1}) string) *Value { + + groups := make(map[string][]{1}) + + v.Each{4}(func(index int, val {1}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]{1}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data:groups} + +} + +// Replace{4} uses the specified function to replace each {1}s +// by iterating each item. The data in the returned result will be a +// []{1} containing the replaced items. +func (v *Value) Replace{4}(replacer func(int, {1}) {1}) *Value { + + arr := v.Must{4}Slice() + replaced := make([]{1}, len(arr)) + + v.Each{4}(func(index int, val {1}) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data:replaced} + +} + +// Collect{4} uses the specified collector function to collect a value +// for each of the {1}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) Collect{4}(collector func(int, {1}) interface{}) *Value { + + arr := v.Must{4}Slice() + collected := make([]interface{}, len(arr)) + + v.Each{4}(func(index int, val {1}) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data:collected} +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func Test{4}(t *testing.T) { + + val := {1}( {2} ) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").{4}()) + assert.Equal(t, val, New(m).Get("value").Must{4}()) + assert.Equal(t, {1}({3}), New(m).Get("nothing").{4}()) + assert.Equal(t, val, New(m).Get("nothing").{4}({2})) + + assert.Panics(t, func() { + New(m).Get("age").Must{4}() + }) + +} + +func Test{4}Slice(t *testing.T) { + + val := {1}( {2} ) + m := map[string]interface{}{"value": []{1}{ val }, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").{4}Slice()[0]) + assert.Equal(t, val, New(m).Get("value").Must{4}Slice()[0]) + assert.Equal(t, []{1}(nil), New(m).Get("nothing").{4}Slice()) + assert.Equal(t, val, New(m).Get("nothing").{4}Slice( []{1}{ {1}({2}) } )[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").Must{4}Slice() + }) + +} + +func TestIs{4}(t *testing.T) { + + var v *Value + + v = &Value{data: {1}({2})} + assert.True(t, v.Is{4}()) + + v = &Value{data: []{1}{ {1}({2}) }} + assert.True(t, v.Is{4}Slice()) + +} + +func TestEach{4}(t *testing.T) { + + v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} + count := 0 + replacedVals := make([]{1}, 0) + assert.Equal(t, v, v.Each{4}(func(i int, val {1}) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.Must{4}Slice()[0]) + assert.Equal(t, replacedVals[1], v.Must{4}Slice()[1]) + assert.Equal(t, replacedVals[2], v.Must{4}Slice()[2]) + +} + +func TestWhere{4}(t *testing.T) { + + v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} + + selected := v.Where{4}(func(i int, val {1}) bool { + return i%2==0 + }).Must{4}Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroup{4}(t *testing.T) { + + v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} + + grouped := v.Group{4}(func(i int, val {1}) string { + return fmt.Sprintf("%v", i%2==0) + }).data.(map[string][]{1}) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplace{4}(t *testing.T) { + + v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} + + rawArr := v.Must{4}Slice() + + replaced := v.Replace{4}(func(index int, val {1}) {1} { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.Must{4}Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollect{4}(t *testing.T) { + + v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} + + collected := v.Collect{4}(func(index int, val {1}) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} diff --git a/third_party/src/github.com/stretchr/objx/codegen/types_list.txt b/third_party/src/github.com/stretchr/objx/codegen/types_list.txt new file mode 100644 index 00000000000..069d43d8ecf --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/codegen/types_list.txt @@ -0,0 +1,20 @@ +Interface,interface{},"something",nil,Inter +Map,map[string]interface{},map[string]interface{}{"name":"Tyler"},nil,MSI +ObjxMap,(Map),New(1),New(nil),ObjxMap +Bool,bool,true,false,Bool +String,string,"hello","",Str +Int,int,1,0,Int +Int8,int8,1,0,Int8 +Int16,int16,1,0,Int16 +Int32,int32,1,0,Int32 +Int64,int64,1,0,Int64 +Uint,uint,1,0,Uint +Uint8,uint8,1,0,Uint8 +Uint16,uint16,1,0,Uint16 +Uint32,uint32,1,0,Uint32 +Uint64,uint64,1,0,Uint64 +Uintptr,uintptr,1,0,Uintptr +Float32,float32,1,0,Float32 +Float64,float64,1,0,Float64 +Complex64,complex64,1,0,Complex64 +Complex128,complex128,1,0,Complex128 diff --git a/third_party/src/github.com/stretchr/objx/constants.go b/third_party/src/github.com/stretchr/objx/constants.go new file mode 100644 index 00000000000..f9eb42a25e0 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/constants.go @@ -0,0 +1,13 @@ +package objx + +const ( + // PathSeparator is the character used to separate the elements + // of the keypath. + // + // For example, `location.address.city` + PathSeparator string = "." + + // SignatureSeparator is the character that is used to + // separate the Base64 string from the security signature. + SignatureSeparator = "_" +) diff --git a/third_party/src/github.com/stretchr/objx/conversions.go b/third_party/src/github.com/stretchr/objx/conversions.go new file mode 100644 index 00000000000..9cdfa9f9f61 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/conversions.go @@ -0,0 +1,117 @@ +package objx + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +// JSON converts the contained object to a JSON string +// representation +func (m Map) JSON() (string, error) { + + result, err := json.Marshal(m) + + if err != nil { + err = errors.New("objx: JSON encode failed with: " + err.Error()) + } + + return string(result), err + +} + +// MustJSON converts the contained object to a JSON string +// representation and panics if there is an error +func (m Map) MustJSON() string { + result, err := m.JSON() + if err != nil { + panic(err.Error()) + } + return result +} + +// Base64 converts the contained object to a Base64 string +// representation of the JSON string representation +func (m Map) Base64() (string, error) { + + var buf bytes.Buffer + + jsonData, err := m.JSON() + if err != nil { + return "", err + } + + encoder := base64.NewEncoder(base64.StdEncoding, &buf) + encoder.Write([]byte(jsonData)) + encoder.Close() + + return buf.String(), nil + +} + +// MustBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and panics +// if there is an error +func (m Map) MustBase64() string { + result, err := m.Base64() + if err != nil { + panic(err.Error()) + } + return result +} + +// SignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key. +func (m Map) SignedBase64(key string) (string, error) { + + base64, err := m.Base64() + if err != nil { + return "", err + } + + sig := HashWithKey(base64, key) + + return base64 + SignatureSeparator + sig, nil + +} + +// MustSignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key and panics if there is an error +func (m Map) MustSignedBase64(key string) string { + result, err := m.SignedBase64(key) + if err != nil { + panic(err.Error()) + } + return result +} + +/* + URL Query + ------------------------------------------------ +*/ + +// URLValues creates a url.Values object from an Obj. This +// function requires that the wrapped object be a map[string]interface{} +func (m Map) URLValues() url.Values { + + vals := make(url.Values) + + for k, v := range m { + //TODO: can this be done without sprintf? + vals.Set(k, fmt.Sprintf("%v", v)) + } + + return vals +} + +// URLQuery gets an encoded URL query representing the given +// Obj. This function requires that the wrapped object be a +// map[string]interface{} +func (m Map) URLQuery() (string, error) { + return m.URLValues().Encode(), nil +} diff --git a/third_party/src/github.com/stretchr/objx/conversions_test.go b/third_party/src/github.com/stretchr/objx/conversions_test.go new file mode 100644 index 00000000000..e9ccd2987b6 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/conversions_test.go @@ -0,0 +1,94 @@ +package objx + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestConversionJSON(t *testing.T) { + + jsonString := `{"name":"Mat"}` + o := MustFromJSON(jsonString) + + result, err := o.JSON() + + if assert.NoError(t, err) { + assert.Equal(t, jsonString, result) + } + + assert.Equal(t, jsonString, o.MustJSON()) + +} + +func TestConversionJSONWithError(t *testing.T) { + + o := MSI() + o["test"] = func() {} + + assert.Panics(t, func() { + o.MustJSON() + }) + + _, err := o.JSON() + + assert.Error(t, err) + +} + +func TestConversionBase64(t *testing.T) { + + o := New(map[string]interface{}{"name": "Mat"}) + + result, err := o.Base64() + + if assert.NoError(t, err) { + assert.Equal(t, "eyJuYW1lIjoiTWF0In0=", result) + } + + assert.Equal(t, "eyJuYW1lIjoiTWF0In0=", o.MustBase64()) + +} + +func TestConversionBase64WithError(t *testing.T) { + + o := MSI() + o["test"] = func() {} + + assert.Panics(t, func() { + o.MustBase64() + }) + + _, err := o.Base64() + + assert.Error(t, err) + +} + +func TestConversionSignedBase64(t *testing.T) { + + o := New(map[string]interface{}{"name": "Mat"}) + + result, err := o.SignedBase64("key") + + if assert.NoError(t, err) { + assert.Equal(t, "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6", result) + } + + assert.Equal(t, "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6", o.MustSignedBase64("key")) + +} + +func TestConversionSignedBase64WithError(t *testing.T) { + + o := MSI() + o["test"] = func() {} + + assert.Panics(t, func() { + o.MustSignedBase64("key") + }) + + _, err := o.SignedBase64("key") + + assert.Error(t, err) + +} diff --git a/third_party/src/github.com/stretchr/objx/doc.go b/third_party/src/github.com/stretchr/objx/doc.go new file mode 100644 index 00000000000..47bf85e4634 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/doc.go @@ -0,0 +1,72 @@ +// objx - Go package for dealing with maps, slices, JSON and other data. +// +// Overview +// +// Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes +// a powerful `Get` method (among others) that allows you to easily and quickly get +// access to data within the map, without having to worry too much about type assertions, +// missing data, default values etc. +// +// Pattern +// +// Objx uses a preditable pattern to make access data from within `map[string]interface{}'s +// easy. +// +// Call one of the `objx.` functions to create your `objx.Map` to get going: +// +// m, err := objx.FromJSON(json) +// +// NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, +// the rest will be optimistic and try to figure things out without panicking. +// +// Use `Get` to access the value you're interested in. You can use dot and array +// notation too: +// +// m.Get("places[0].latlng") +// +// Once you have saught the `Value` you're interested in, you can use the `Is*` methods +// to determine its type. +// +// if m.Get("code").IsStr() { /* ... */ } +// +// Or you can just assume the type, and use one of the strong type methods to +// extract the real value: +// +// m.Get("code").Int() +// +// If there's no value there (or if it's the wrong type) then a default value +// will be returned, or you can be explicit about the default value. +// +// Get("code").Int(-1) +// +// If you're dealing with a slice of data as a value, Objx provides many useful +// methods for iterating, manipulating and selecting that data. You can find out more +// by exploring the index below. +// +// Reading data +// +// A simple example of how to use Objx: +// +// // use MustFromJSON to make an objx.Map from some JSON +// m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) +// +// // get the details +// name := m.Get("name").Str() +// age := m.Get("age").Int() +// +// // get their nickname (or use their name if they +// // don't have one) +// nickname := m.Get("nickname").Str(name) +// +// Ranging +// +// Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For +// example, to `range` the data, do what you would expect: +// +// m := objx.MustFromJSON(json) +// for key, value := range m { +// +// /* ... do your magic ... */ +// +// } +package objx diff --git a/third_party/src/github.com/stretchr/objx/fixture_test.go b/third_party/src/github.com/stretchr/objx/fixture_test.go new file mode 100644 index 00000000000..27f7d9049a0 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/fixture_test.go @@ -0,0 +1,98 @@ +package objx + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +var fixtures = []struct { + // name is the name of the fixture (used for reporting + // failures) + name string + // data is the JSON data to be worked on + data string + // get is the argument(s) to pass to Get + get interface{} + // output is the expected output + output interface{} +}{ + { + name: "Simple get", + data: `{"name": "Mat"}`, + get: "name", + output: "Mat", + }, + { + name: "Get with dot notation", + data: `{"address": {"city": "Boulder"}}`, + get: "address.city", + output: "Boulder", + }, + { + name: "Deep get with dot notation", + data: `{"one": {"two": {"three": {"four": "hello"}}}}`, + get: "one.two.three.four", + output: "hello", + }, + { + name: "Get missing with dot notation", + data: `{"one": {"two": {"three": {"four": "hello"}}}}`, + get: "one.ten", + output: nil, + }, + { + name: "Get with array notation", + data: `{"tags": ["one", "two", "three"]}`, + get: "tags[1]", + output: "two", + }, + { + name: "Get with array and dot notation", + data: `{"types": { "tags": ["one", "two", "three"]}}`, + get: "types.tags[1]", + output: "two", + }, + { + name: "Get with array and dot notation - field after array", + data: `{"tags": [{"name":"one"}, {"name":"two"}, {"name":"three"}]}`, + get: "tags[1].name", + output: "two", + }, + { + name: "Complex get with array and dot notation", + data: `{"tags": [{"list": [{"one":"pizza"}]}]}`, + get: "tags[0].list[0].one", + output: "pizza", + }, + { + name: "Get field from within string should be nil", + data: `{"name":"Tyler"}`, + get: "name.something", + output: nil, + }, + { + name: "Get field from within string (using array accessor) should be nil", + data: `{"numbers":["one", "two", "three"]}`, + get: "numbers[0].nope", + output: nil, + }, +} + +func TestFixtures(t *testing.T) { + + for _, fixture := range fixtures { + + m := MustFromJSON(fixture.data) + + // get the value + t.Logf("Running get fixture: \"%s\" (%v)", fixture.name, fixture) + value := m.Get(fixture.get.(string)) + + // make sure it matches + assert.Equal(t, fixture.output, value.data, + "Get fixture \"%s\" failed: %v", fixture.name, fixture, + ) + + } + +} diff --git a/third_party/src/github.com/stretchr/objx/map.go b/third_party/src/github.com/stretchr/objx/map.go new file mode 100644 index 00000000000..eb6ed8e285c --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/map.go @@ -0,0 +1,222 @@ +package objx + +import ( + "encoding/base64" + "encoding/json" + "errors" + "io/ioutil" + "net/url" + "strings" +) + +// MSIConvertable is an interface that defines methods for converting your +// custom types to a map[string]interface{} representation. +type MSIConvertable interface { + // MSI gets a map[string]interface{} (msi) representing the + // object. + MSI() map[string]interface{} +} + +// Map provides extended functionality for working with +// untyped data, in particular map[string]interface (msi). +type Map map[string]interface{} + +// Value returns the internal value instance +func (m Map) Value() *Value { + return &Value{data: m} +} + +// Nil represents a nil Map. +var Nil Map = New(nil) + +// New creates a new Map containing the map[string]interface{} in the data argument. +// If the data argument is not a map[string]interface, New attempts to call the +// MSI() method on the MSIConvertable interface to create one. +func New(data interface{}) Map { + if _, ok := data.(map[string]interface{}); !ok { + if converter, ok := data.(MSIConvertable); ok { + data = converter.MSI() + } else { + return nil + } + } + return Map(data.(map[string]interface{})) +} + +// MSI creates a map[string]interface{} and puts it inside a new Map. +// +// The arguments follow a key, value pattern. +// +// Panics +// +// Panics if any key arugment is non-string or if there are an odd number of arguments. +// +// Example +// +// To easily create Maps: +// +// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true)) +// +// // creates an Map equivalent to +// m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}}) +func MSI(keyAndValuePairs ...interface{}) Map { + + newMap := make(map[string]interface{}) + keyAndValuePairsLen := len(keyAndValuePairs) + + if keyAndValuePairsLen%2 != 0 { + panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.") + } + + for i := 0; i < keyAndValuePairsLen; i = i + 2 { + + key := keyAndValuePairs[i] + value := keyAndValuePairs[i+1] + + // make sure the key is a string + keyString, keyStringOK := key.(string) + if !keyStringOK { + panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.") + } + + newMap[keyString] = value + + } + + return New(newMap) +} + +// ****** Conversion Constructors + +// MustFromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Panics if the JSON is invalid. +func MustFromJSON(jsonString string) Map { + o, err := FromJSON(jsonString) + + if err != nil { + panic("objx: MustFromJSON failed with error: " + err.Error()) + } + + return o +} + +// FromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Returns an error if the JSON is invalid. +func FromJSON(jsonString string) (Map, error) { + + var data interface{} + err := json.Unmarshal([]byte(jsonString), &data) + + if err != nil { + return Nil, err + } + + return New(data), nil + +} + +// FromBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by Base64 +func FromBase64(base64String string) (Map, error) { + + decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String)) + + decoded, err := ioutil.ReadAll(decoder) + if err != nil { + return nil, err + } + + return FromJSON(string(decoded)) +} + +// MustFromBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromBase64(base64String string) Map { + + result, err := FromBase64(base64String) + + if err != nil { + panic("objx: MustFromBase64 failed with error: " + err.Error()) + } + + return result +} + +// FromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by SignedBase64 +func FromSignedBase64(base64String, key string) (Map, error) { + parts := strings.Split(base64String, SignatureSeparator) + if len(parts) != 2 { + return nil, errors.New("objx: Signed base64 string is malformed.") + } + + sig := HashWithKey(parts[0], key) + if parts[1] != sig { + return nil, errors.New("objx: Signature for base64 data does not match.") + } + + return FromBase64(parts[0]) +} + +// MustFromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromSignedBase64(base64String, key string) Map { + + result, err := FromSignedBase64(base64String, key) + + if err != nil { + panic("objx: MustFromSignedBase64 failed with error: " + err.Error()) + } + + return result +} + +// FromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +func FromURLQuery(query string) (Map, error) { + + vals, err := url.ParseQuery(query) + + if err != nil { + return nil, err + } + + m := make(map[string]interface{}) + for k, vals := range vals { + m[k] = vals[0] + } + + return New(m), nil +} + +// MustFromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +// +// Panics if it encounters an error +func MustFromURLQuery(query string) Map { + + o, err := FromURLQuery(query) + + if err != nil { + panic("objx: MustFromURLQuery failed with error: " + err.Error()) + } + + return o + +} diff --git a/third_party/src/github.com/stretchr/objx/map_for_test.go b/third_party/src/github.com/stretchr/objx/map_for_test.go new file mode 100644 index 00000000000..6beb5067569 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/map_for_test.go @@ -0,0 +1,10 @@ +package objx + +var TestMap map[string]interface{} = map[string]interface{}{ + "name": "Tyler", + "address": map[string]interface{}{ + "city": "Salt Lake City", + "state": "UT", + }, + "numbers": []interface{}{"one", "two", "three", "four", "five"}, +} diff --git a/third_party/src/github.com/stretchr/objx/map_test.go b/third_party/src/github.com/stretchr/objx/map_test.go new file mode 100644 index 00000000000..1f8b45c6170 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/map_test.go @@ -0,0 +1,147 @@ +package objx + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +type Convertable struct { + name string +} + +func (c *Convertable) MSI() map[string]interface{} { + return map[string]interface{}{"name": c.name} +} + +type Unconvertable struct { + name string +} + +func TestMapCreation(t *testing.T) { + + o := New(nil) + assert.Nil(t, o) + + o = New("Tyler") + assert.Nil(t, o) + + unconvertable := &Unconvertable{name: "Tyler"} + o = New(unconvertable) + assert.Nil(t, o) + + convertable := &Convertable{name: "Tyler"} + o = New(convertable) + if assert.NotNil(t, convertable) { + assert.Equal(t, "Tyler", o["name"], "Tyler") + } + + o = MSI() + if assert.NotNil(t, o) { + assert.NotNil(t, o) + } + + o = MSI("name", "Tyler") + if assert.NotNil(t, o) { + if assert.NotNil(t, o) { + assert.Equal(t, o["name"], "Tyler") + } + } + +} + +func TestMapMustFromJSONWithError(t *testing.T) { + + _, err := FromJSON(`"name":"Mat"}`) + assert.Error(t, err) + +} + +func TestMapFromJSON(t *testing.T) { + + o := MustFromJSON(`{"name":"Mat"}`) + + if assert.NotNil(t, o) { + if assert.NotNil(t, o) { + assert.Equal(t, "Mat", o["name"]) + } + } + +} + +func TestMapFromJSONWithError(t *testing.T) { + + var m Map + + assert.Panics(t, func() { + m = MustFromJSON(`"name":"Mat"}`) + }) + + assert.Nil(t, m) + +} + +func TestMapFromBase64String(t *testing.T) { + + base64String := "eyJuYW1lIjoiTWF0In0=" + + o, err := FromBase64(base64String) + + if assert.NoError(t, err) { + assert.Equal(t, o.Get("name").Str(), "Mat") + } + + assert.Equal(t, MustFromBase64(base64String).Get("name").Str(), "Mat") + +} + +func TestMapFromBase64StringWithError(t *testing.T) { + + base64String := "eyJuYW1lIjoiTWFasd0In0=" + + _, err := FromBase64(base64String) + + assert.Error(t, err) + + assert.Panics(t, func() { + MustFromBase64(base64String) + }) + +} + +func TestMapFromSignedBase64String(t *testing.T) { + + base64String := "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6" + + o, err := FromSignedBase64(base64String, "key") + + if assert.NoError(t, err) { + assert.Equal(t, o.Get("name").Str(), "Mat") + } + + assert.Equal(t, MustFromSignedBase64(base64String, "key").Get("name").Str(), "Mat") + +} + +func TestMapFromSignedBase64StringWithError(t *testing.T) { + + base64String := "eyJuYW1lasdIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6" + + _, err := FromSignedBase64(base64String, "key") + + assert.Error(t, err) + + assert.Panics(t, func() { + MustFromSignedBase64(base64String, "key") + }) + +} + +func TestMapFromURLQuery(t *testing.T) { + + m, err := FromURLQuery("name=tyler&state=UT") + if assert.NoError(t, err) && assert.NotNil(t, m) { + assert.Equal(t, "tyler", m.Get("name").Str()) + assert.Equal(t, "UT", m.Get("state").Str()) + } + +} diff --git a/third_party/src/github.com/stretchr/objx/mutations.go b/third_party/src/github.com/stretchr/objx/mutations.go new file mode 100644 index 00000000000..b35c86392bf --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/mutations.go @@ -0,0 +1,81 @@ +package objx + +// Exclude returns a new Map with the keys in the specified []string +// excluded. +func (d Map) Exclude(exclude []string) Map { + + excluded := make(Map) + for k, v := range d { + var shouldInclude bool = true + for _, toExclude := range exclude { + if k == toExclude { + shouldInclude = false + break + } + } + if shouldInclude { + excluded[k] = v + } + } + + return excluded +} + +// Copy creates a shallow copy of the Obj. +func (m Map) Copy() Map { + copied := make(map[string]interface{}) + for k, v := range m { + copied[k] = v + } + return New(copied) +} + +// Merge blends the specified map with a copy of this map and returns the result. +// +// Keys that appear in both will be selected from the specified map. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) Merge(merge Map) Map { + return m.Copy().MergeHere(merge) +} + +// Merge blends the specified map with this map and returns the current map. +// +// Keys that appear in both will be selected from the specified map. The original map +// will be modified. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) MergeHere(merge Map) Map { + + for k, v := range merge { + m[k] = v + } + + return m + +} + +// Transform builds a new Obj giving the transformer a chance +// to change the keys and values as it goes. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map { + newMap := make(map[string]interface{}) + for k, v := range m { + modifiedKey, modifiedVal := transformer(k, v) + newMap[modifiedKey] = modifiedVal + } + return New(newMap) +} + +// TransformKeys builds a new map using the specified key mapping. +// +// Unspecified keys will be unaltered. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) TransformKeys(mapping map[string]string) Map { + return m.Transform(func(key string, value interface{}) (string, interface{}) { + + if newKey, ok := mapping[key]; ok { + return newKey, value + } + + return key, value + }) +} diff --git a/third_party/src/github.com/stretchr/objx/mutations_test.go b/third_party/src/github.com/stretchr/objx/mutations_test.go new file mode 100644 index 00000000000..e20ee23bc4b --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/mutations_test.go @@ -0,0 +1,77 @@ +package objx + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestExclude(t *testing.T) { + + d := make(Map) + d["name"] = "Mat" + d["age"] = 29 + d["secret"] = "ABC" + + excluded := d.Exclude([]string{"secret"}) + + assert.Equal(t, d["name"], excluded["name"]) + assert.Equal(t, d["age"], excluded["age"]) + assert.False(t, excluded.Has("secret"), "secret should be excluded") + +} + +func TestCopy(t *testing.T) { + + d1 := make(map[string]interface{}) + d1["name"] = "Tyler" + d1["location"] = "UT" + + d1Obj := New(d1) + d2Obj := d1Obj.Copy() + + d2Obj["name"] = "Mat" + + assert.Equal(t, d1Obj.Get("name").Str(), "Tyler") + assert.Equal(t, d2Obj.Get("name").Str(), "Mat") + +} + +func TestMerge(t *testing.T) { + + d := make(map[string]interface{}) + d["name"] = "Mat" + + d1 := make(map[string]interface{}) + d1["name"] = "Tyler" + d1["location"] = "UT" + + dObj := New(d) + d1Obj := New(d1) + + merged := dObj.Merge(d1Obj) + + assert.Equal(t, merged.Get("name").Str(), d1Obj.Get("name").Str()) + assert.Equal(t, merged.Get("location").Str(), d1Obj.Get("location").Str()) + assert.Empty(t, dObj.Get("location").Str()) + +} + +func TestMergeHere(t *testing.T) { + + d := make(map[string]interface{}) + d["name"] = "Mat" + + d1 := make(map[string]interface{}) + d1["name"] = "Tyler" + d1["location"] = "UT" + + dObj := New(d) + d1Obj := New(d1) + + merged := dObj.MergeHere(d1Obj) + + assert.Equal(t, dObj, merged, "With MergeHere, it should return the first modified map") + assert.Equal(t, merged.Get("name").Str(), d1Obj.Get("name").Str()) + assert.Equal(t, merged.Get("location").Str(), d1Obj.Get("location").Str()) + assert.Equal(t, merged.Get("location").Str(), dObj.Get("location").Str()) +} diff --git a/third_party/src/github.com/stretchr/objx/security.go b/third_party/src/github.com/stretchr/objx/security.go new file mode 100644 index 00000000000..fdd6be9cfb3 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/security.go @@ -0,0 +1,14 @@ +package objx + +import ( + "crypto/sha1" + "encoding/hex" +) + +// HashWithKey hashes the specified string using the security +// key. +func HashWithKey(data, key string) string { + hash := sha1.New() + hash.Write([]byte(data + ":" + key)) + return hex.EncodeToString(hash.Sum(nil)) +} diff --git a/third_party/src/github.com/stretchr/objx/security_test.go b/third_party/src/github.com/stretchr/objx/security_test.go new file mode 100644 index 00000000000..8f0898f62ca --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/security_test.go @@ -0,0 +1,12 @@ +package objx + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestHashWithKey(t *testing.T) { + + assert.Equal(t, "0ce84d8d01f2c7b6e0882b784429c54d280ea2d9", HashWithKey("abc", "def")) + +} diff --git a/third_party/src/github.com/stretchr/objx/simple_example_test.go b/third_party/src/github.com/stretchr/objx/simple_example_test.go new file mode 100644 index 00000000000..5408c7fd3d3 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/simple_example_test.go @@ -0,0 +1,41 @@ +package objx + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSimpleExample(t *testing.T) { + + // build a map from a JSON object + o := MustFromJSON(`{"name":"Mat","foods":["indian","chinese"], "location":{"county":"hobbiton","city":"the shire"}}`) + + // Map can be used as a straight map[string]interface{} + assert.Equal(t, o["name"], "Mat") + + // Get an Value object + v := o.Get("name") + assert.Equal(t, v, &Value{data: "Mat"}) + + // Test the contained value + assert.False(t, v.IsInt()) + assert.False(t, v.IsBool()) + assert.True(t, v.IsStr()) + + // Get the contained value + assert.Equal(t, v.Str(), "Mat") + + // Get a default value if the contained value is not of the expected type or does not exist + assert.Equal(t, 1, v.Int(1)) + + // Get a value by using array notation + assert.Equal(t, "indian", o.Get("foods[0]").Data()) + + // Set a value by using array notation + o.Set("foods[0]", "italian") + assert.Equal(t, "italian", o.Get("foods[0]").Str()) + + // Get a value by using dot notation + assert.Equal(t, "hobbiton", o.Get("location.county").Str()) + +} diff --git a/third_party/src/github.com/stretchr/objx/tests.go b/third_party/src/github.com/stretchr/objx/tests.go new file mode 100644 index 00000000000..d9e0b479a4c --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/tests.go @@ -0,0 +1,17 @@ +package objx + +// Has gets whether there is something at the specified selector +// or not. +// +// If m is nil, Has will always return false. +func (m Map) Has(selector string) bool { + if m == nil { + return false + } + return !m.Get(selector).IsNil() +} + +// IsNil gets whether the data is nil or not. +func (v *Value) IsNil() bool { + return v == nil || v.data == nil +} diff --git a/third_party/src/github.com/stretchr/objx/tests_test.go b/third_party/src/github.com/stretchr/objx/tests_test.go new file mode 100644 index 00000000000..bcc1eb03d08 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/tests_test.go @@ -0,0 +1,24 @@ +package objx + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestHas(t *testing.T) { + + m := New(TestMap) + + assert.True(t, m.Has("name")) + assert.True(t, m.Has("address.state")) + assert.True(t, m.Has("numbers[4]")) + + assert.False(t, m.Has("address.state.nope")) + assert.False(t, m.Has("address.nope")) + assert.False(t, m.Has("nope")) + assert.False(t, m.Has("numbers[5]")) + + m = nil + assert.False(t, m.Has("nothing")) + +} diff --git a/third_party/src/github.com/stretchr/objx/type_specific_codegen.go b/third_party/src/github.com/stretchr/objx/type_specific_codegen.go new file mode 100644 index 00000000000..f3ecb29b950 --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/type_specific_codegen.go @@ -0,0 +1,2881 @@ +package objx + +/* + Inter (interface{} and []interface{}) + -------------------------------------------------- +*/ + +// Inter gets the value as a interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Inter(optionalDefault ...interface{}) interface{} { + if s, ok := v.data.(interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInter gets the value as a interface{}. +// +// Panics if the object is not a interface{}. +func (v *Value) MustInter() interface{} { + return v.data.(interface{}) +} + +// InterSlice gets the value as a []interface{}, returns the optionalDefault +// value or nil if the value is not a []interface{}. +func (v *Value) InterSlice(optionalDefault ...[]interface{}) []interface{} { + if s, ok := v.data.([]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInterSlice gets the value as a []interface{}. +// +// Panics if the object is not a []interface{}. +func (v *Value) MustInterSlice() []interface{} { + return v.data.([]interface{}) +} + +// IsInter gets whether the object contained is a interface{} or not. +func (v *Value) IsInter() bool { + _, ok := v.data.(interface{}) + return ok +} + +// IsInterSlice gets whether the object contained is a []interface{} or not. +func (v *Value) IsInterSlice() bool { + _, ok := v.data.([]interface{}) + return ok +} + +// EachInter calls the specified callback for each object +// in the []interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachInter(callback func(int, interface{}) bool) *Value { + + for index, val := range v.MustInterSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInter uses the specified decider function to select items +// from the []interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInter(decider func(int, interface{}) bool) *Value { + + var selected []interface{} + + v.EachInter(func(index int, val interface{}) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInter uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]interface{}. +func (v *Value) GroupInter(grouper func(int, interface{}) string) *Value { + + groups := make(map[string][]interface{}) + + v.EachInter(func(index int, val interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInter uses the specified function to replace each interface{}s +// by iterating each item. The data in the returned result will be a +// []interface{} containing the replaced items. +func (v *Value) ReplaceInter(replacer func(int, interface{}) interface{}) *Value { + + arr := v.MustInterSlice() + replaced := make([]interface{}, len(arr)) + + v.EachInter(func(index int, val interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInter uses the specified collector function to collect a value +// for each of the interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInter(collector func(int, interface{}) interface{}) *Value { + + arr := v.MustInterSlice() + collected := make([]interface{}, len(arr)) + + v.EachInter(func(index int, val interface{}) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + MSI (map[string]interface{} and []map[string]interface{}) + -------------------------------------------------- +*/ + +// MSI gets the value as a map[string]interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) MSI(optionalDefault ...map[string]interface{}) map[string]interface{} { + if s, ok := v.data.(map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSI gets the value as a map[string]interface{}. +// +// Panics if the object is not a map[string]interface{}. +func (v *Value) MustMSI() map[string]interface{} { + return v.data.(map[string]interface{}) +} + +// MSISlice gets the value as a []map[string]interface{}, returns the optionalDefault +// value or nil if the value is not a []map[string]interface{}. +func (v *Value) MSISlice(optionalDefault ...[]map[string]interface{}) []map[string]interface{} { + if s, ok := v.data.([]map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSISlice gets the value as a []map[string]interface{}. +// +// Panics if the object is not a []map[string]interface{}. +func (v *Value) MustMSISlice() []map[string]interface{} { + return v.data.([]map[string]interface{}) +} + +// IsMSI gets whether the object contained is a map[string]interface{} or not. +func (v *Value) IsMSI() bool { + _, ok := v.data.(map[string]interface{}) + return ok +} + +// IsMSISlice gets whether the object contained is a []map[string]interface{} or not. +func (v *Value) IsMSISlice() bool { + _, ok := v.data.([]map[string]interface{}) + return ok +} + +// EachMSI calls the specified callback for each object +// in the []map[string]interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachMSI(callback func(int, map[string]interface{}) bool) *Value { + + for index, val := range v.MustMSISlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereMSI uses the specified decider function to select items +// from the []map[string]interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereMSI(decider func(int, map[string]interface{}) bool) *Value { + + var selected []map[string]interface{} + + v.EachMSI(func(index int, val map[string]interface{}) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupMSI uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]map[string]interface{}. +func (v *Value) GroupMSI(grouper func(int, map[string]interface{}) string) *Value { + + groups := make(map[string][]map[string]interface{}) + + v.EachMSI(func(index int, val map[string]interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]map[string]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceMSI uses the specified function to replace each map[string]interface{}s +// by iterating each item. The data in the returned result will be a +// []map[string]interface{} containing the replaced items. +func (v *Value) ReplaceMSI(replacer func(int, map[string]interface{}) map[string]interface{}) *Value { + + arr := v.MustMSISlice() + replaced := make([]map[string]interface{}, len(arr)) + + v.EachMSI(func(index int, val map[string]interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectMSI uses the specified collector function to collect a value +// for each of the map[string]interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectMSI(collector func(int, map[string]interface{}) interface{}) *Value { + + arr := v.MustMSISlice() + collected := make([]interface{}, len(arr)) + + v.EachMSI(func(index int, val map[string]interface{}) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + ObjxMap ((Map) and [](Map)) + -------------------------------------------------- +*/ + +// ObjxMap gets the value as a (Map), returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) ObjxMap(optionalDefault ...(Map)) Map { + if s, ok := v.data.((Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return New(nil) +} + +// MustObjxMap gets the value as a (Map). +// +// Panics if the object is not a (Map). +func (v *Value) MustObjxMap() Map { + return v.data.((Map)) +} + +// ObjxMapSlice gets the value as a [](Map), returns the optionalDefault +// value or nil if the value is not a [](Map). +func (v *Value) ObjxMapSlice(optionalDefault ...[](Map)) [](Map) { + if s, ok := v.data.([](Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustObjxMapSlice gets the value as a [](Map). +// +// Panics if the object is not a [](Map). +func (v *Value) MustObjxMapSlice() [](Map) { + return v.data.([](Map)) +} + +// IsObjxMap gets whether the object contained is a (Map) or not. +func (v *Value) IsObjxMap() bool { + _, ok := v.data.((Map)) + return ok +} + +// IsObjxMapSlice gets whether the object contained is a [](Map) or not. +func (v *Value) IsObjxMapSlice() bool { + _, ok := v.data.([](Map)) + return ok +} + +// EachObjxMap calls the specified callback for each object +// in the [](Map). +// +// Panics if the object is the wrong type. +func (v *Value) EachObjxMap(callback func(int, Map) bool) *Value { + + for index, val := range v.MustObjxMapSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereObjxMap uses the specified decider function to select items +// from the [](Map). The object contained in the result will contain +// only the selected items. +func (v *Value) WhereObjxMap(decider func(int, Map) bool) *Value { + + var selected [](Map) + + v.EachObjxMap(func(index int, val Map) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupObjxMap uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][](Map). +func (v *Value) GroupObjxMap(grouper func(int, Map) string) *Value { + + groups := make(map[string][](Map)) + + v.EachObjxMap(func(index int, val Map) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([](Map), 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceObjxMap uses the specified function to replace each (Map)s +// by iterating each item. The data in the returned result will be a +// [](Map) containing the replaced items. +func (v *Value) ReplaceObjxMap(replacer func(int, Map) Map) *Value { + + arr := v.MustObjxMapSlice() + replaced := make([](Map), len(arr)) + + v.EachObjxMap(func(index int, val Map) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectObjxMap uses the specified collector function to collect a value +// for each of the (Map)s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectObjxMap(collector func(int, Map) interface{}) *Value { + + arr := v.MustObjxMapSlice() + collected := make([]interface{}, len(arr)) + + v.EachObjxMap(func(index int, val Map) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Bool (bool and []bool) + -------------------------------------------------- +*/ + +// Bool gets the value as a bool, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Bool(optionalDefault ...bool) bool { + if s, ok := v.data.(bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return false +} + +// MustBool gets the value as a bool. +// +// Panics if the object is not a bool. +func (v *Value) MustBool() bool { + return v.data.(bool) +} + +// BoolSlice gets the value as a []bool, returns the optionalDefault +// value or nil if the value is not a []bool. +func (v *Value) BoolSlice(optionalDefault ...[]bool) []bool { + if s, ok := v.data.([]bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustBoolSlice gets the value as a []bool. +// +// Panics if the object is not a []bool. +func (v *Value) MustBoolSlice() []bool { + return v.data.([]bool) +} + +// IsBool gets whether the object contained is a bool or not. +func (v *Value) IsBool() bool { + _, ok := v.data.(bool) + return ok +} + +// IsBoolSlice gets whether the object contained is a []bool or not. +func (v *Value) IsBoolSlice() bool { + _, ok := v.data.([]bool) + return ok +} + +// EachBool calls the specified callback for each object +// in the []bool. +// +// Panics if the object is the wrong type. +func (v *Value) EachBool(callback func(int, bool) bool) *Value { + + for index, val := range v.MustBoolSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereBool uses the specified decider function to select items +// from the []bool. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereBool(decider func(int, bool) bool) *Value { + + var selected []bool + + v.EachBool(func(index int, val bool) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupBool uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]bool. +func (v *Value) GroupBool(grouper func(int, bool) string) *Value { + + groups := make(map[string][]bool) + + v.EachBool(func(index int, val bool) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]bool, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceBool uses the specified function to replace each bools +// by iterating each item. The data in the returned result will be a +// []bool containing the replaced items. +func (v *Value) ReplaceBool(replacer func(int, bool) bool) *Value { + + arr := v.MustBoolSlice() + replaced := make([]bool, len(arr)) + + v.EachBool(func(index int, val bool) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectBool uses the specified collector function to collect a value +// for each of the bools in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectBool(collector func(int, bool) interface{}) *Value { + + arr := v.MustBoolSlice() + collected := make([]interface{}, len(arr)) + + v.EachBool(func(index int, val bool) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Str (string and []string) + -------------------------------------------------- +*/ + +// Str gets the value as a string, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Str(optionalDefault ...string) string { + if s, ok := v.data.(string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return "" +} + +// MustStr gets the value as a string. +// +// Panics if the object is not a string. +func (v *Value) MustStr() string { + return v.data.(string) +} + +// StrSlice gets the value as a []string, returns the optionalDefault +// value or nil if the value is not a []string. +func (v *Value) StrSlice(optionalDefault ...[]string) []string { + if s, ok := v.data.([]string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustStrSlice gets the value as a []string. +// +// Panics if the object is not a []string. +func (v *Value) MustStrSlice() []string { + return v.data.([]string) +} + +// IsStr gets whether the object contained is a string or not. +func (v *Value) IsStr() bool { + _, ok := v.data.(string) + return ok +} + +// IsStrSlice gets whether the object contained is a []string or not. +func (v *Value) IsStrSlice() bool { + _, ok := v.data.([]string) + return ok +} + +// EachStr calls the specified callback for each object +// in the []string. +// +// Panics if the object is the wrong type. +func (v *Value) EachStr(callback func(int, string) bool) *Value { + + for index, val := range v.MustStrSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereStr uses the specified decider function to select items +// from the []string. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereStr(decider func(int, string) bool) *Value { + + var selected []string + + v.EachStr(func(index int, val string) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupStr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]string. +func (v *Value) GroupStr(grouper func(int, string) string) *Value { + + groups := make(map[string][]string) + + v.EachStr(func(index int, val string) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]string, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceStr uses the specified function to replace each strings +// by iterating each item. The data in the returned result will be a +// []string containing the replaced items. +func (v *Value) ReplaceStr(replacer func(int, string) string) *Value { + + arr := v.MustStrSlice() + replaced := make([]string, len(arr)) + + v.EachStr(func(index int, val string) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectStr uses the specified collector function to collect a value +// for each of the strings in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectStr(collector func(int, string) interface{}) *Value { + + arr := v.MustStrSlice() + collected := make([]interface{}, len(arr)) + + v.EachStr(func(index int, val string) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int (int and []int) + -------------------------------------------------- +*/ + +// Int gets the value as a int, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int(optionalDefault ...int) int { + if s, ok := v.data.(int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt gets the value as a int. +// +// Panics if the object is not a int. +func (v *Value) MustInt() int { + return v.data.(int) +} + +// IntSlice gets the value as a []int, returns the optionalDefault +// value or nil if the value is not a []int. +func (v *Value) IntSlice(optionalDefault ...[]int) []int { + if s, ok := v.data.([]int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustIntSlice gets the value as a []int. +// +// Panics if the object is not a []int. +func (v *Value) MustIntSlice() []int { + return v.data.([]int) +} + +// IsInt gets whether the object contained is a int or not. +func (v *Value) IsInt() bool { + _, ok := v.data.(int) + return ok +} + +// IsIntSlice gets whether the object contained is a []int or not. +func (v *Value) IsIntSlice() bool { + _, ok := v.data.([]int) + return ok +} + +// EachInt calls the specified callback for each object +// in the []int. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt(callback func(int, int) bool) *Value { + + for index, val := range v.MustIntSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt uses the specified decider function to select items +// from the []int. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt(decider func(int, int) bool) *Value { + + var selected []int + + v.EachInt(func(index int, val int) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int. +func (v *Value) GroupInt(grouper func(int, int) string) *Value { + + groups := make(map[string][]int) + + v.EachInt(func(index int, val int) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt uses the specified function to replace each ints +// by iterating each item. The data in the returned result will be a +// []int containing the replaced items. +func (v *Value) ReplaceInt(replacer func(int, int) int) *Value { + + arr := v.MustIntSlice() + replaced := make([]int, len(arr)) + + v.EachInt(func(index int, val int) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt uses the specified collector function to collect a value +// for each of the ints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt(collector func(int, int) interface{}) *Value { + + arr := v.MustIntSlice() + collected := make([]interface{}, len(arr)) + + v.EachInt(func(index int, val int) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int8 (int8 and []int8) + -------------------------------------------------- +*/ + +// Int8 gets the value as a int8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int8(optionalDefault ...int8) int8 { + if s, ok := v.data.(int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt8 gets the value as a int8. +// +// Panics if the object is not a int8. +func (v *Value) MustInt8() int8 { + return v.data.(int8) +} + +// Int8Slice gets the value as a []int8, returns the optionalDefault +// value or nil if the value is not a []int8. +func (v *Value) Int8Slice(optionalDefault ...[]int8) []int8 { + if s, ok := v.data.([]int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt8Slice gets the value as a []int8. +// +// Panics if the object is not a []int8. +func (v *Value) MustInt8Slice() []int8 { + return v.data.([]int8) +} + +// IsInt8 gets whether the object contained is a int8 or not. +func (v *Value) IsInt8() bool { + _, ok := v.data.(int8) + return ok +} + +// IsInt8Slice gets whether the object contained is a []int8 or not. +func (v *Value) IsInt8Slice() bool { + _, ok := v.data.([]int8) + return ok +} + +// EachInt8 calls the specified callback for each object +// in the []int8. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt8(callback func(int, int8) bool) *Value { + + for index, val := range v.MustInt8Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt8 uses the specified decider function to select items +// from the []int8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt8(decider func(int, int8) bool) *Value { + + var selected []int8 + + v.EachInt8(func(index int, val int8) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int8. +func (v *Value) GroupInt8(grouper func(int, int8) string) *Value { + + groups := make(map[string][]int8) + + v.EachInt8(func(index int, val int8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt8 uses the specified function to replace each int8s +// by iterating each item. The data in the returned result will be a +// []int8 containing the replaced items. +func (v *Value) ReplaceInt8(replacer func(int, int8) int8) *Value { + + arr := v.MustInt8Slice() + replaced := make([]int8, len(arr)) + + v.EachInt8(func(index int, val int8) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt8 uses the specified collector function to collect a value +// for each of the int8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt8(collector func(int, int8) interface{}) *Value { + + arr := v.MustInt8Slice() + collected := make([]interface{}, len(arr)) + + v.EachInt8(func(index int, val int8) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int16 (int16 and []int16) + -------------------------------------------------- +*/ + +// Int16 gets the value as a int16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int16(optionalDefault ...int16) int16 { + if s, ok := v.data.(int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt16 gets the value as a int16. +// +// Panics if the object is not a int16. +func (v *Value) MustInt16() int16 { + return v.data.(int16) +} + +// Int16Slice gets the value as a []int16, returns the optionalDefault +// value or nil if the value is not a []int16. +func (v *Value) Int16Slice(optionalDefault ...[]int16) []int16 { + if s, ok := v.data.([]int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt16Slice gets the value as a []int16. +// +// Panics if the object is not a []int16. +func (v *Value) MustInt16Slice() []int16 { + return v.data.([]int16) +} + +// IsInt16 gets whether the object contained is a int16 or not. +func (v *Value) IsInt16() bool { + _, ok := v.data.(int16) + return ok +} + +// IsInt16Slice gets whether the object contained is a []int16 or not. +func (v *Value) IsInt16Slice() bool { + _, ok := v.data.([]int16) + return ok +} + +// EachInt16 calls the specified callback for each object +// in the []int16. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt16(callback func(int, int16) bool) *Value { + + for index, val := range v.MustInt16Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt16 uses the specified decider function to select items +// from the []int16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt16(decider func(int, int16) bool) *Value { + + var selected []int16 + + v.EachInt16(func(index int, val int16) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int16. +func (v *Value) GroupInt16(grouper func(int, int16) string) *Value { + + groups := make(map[string][]int16) + + v.EachInt16(func(index int, val int16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt16 uses the specified function to replace each int16s +// by iterating each item. The data in the returned result will be a +// []int16 containing the replaced items. +func (v *Value) ReplaceInt16(replacer func(int, int16) int16) *Value { + + arr := v.MustInt16Slice() + replaced := make([]int16, len(arr)) + + v.EachInt16(func(index int, val int16) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt16 uses the specified collector function to collect a value +// for each of the int16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt16(collector func(int, int16) interface{}) *Value { + + arr := v.MustInt16Slice() + collected := make([]interface{}, len(arr)) + + v.EachInt16(func(index int, val int16) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int32 (int32 and []int32) + -------------------------------------------------- +*/ + +// Int32 gets the value as a int32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int32(optionalDefault ...int32) int32 { + if s, ok := v.data.(int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt32 gets the value as a int32. +// +// Panics if the object is not a int32. +func (v *Value) MustInt32() int32 { + return v.data.(int32) +} + +// Int32Slice gets the value as a []int32, returns the optionalDefault +// value or nil if the value is not a []int32. +func (v *Value) Int32Slice(optionalDefault ...[]int32) []int32 { + if s, ok := v.data.([]int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt32Slice gets the value as a []int32. +// +// Panics if the object is not a []int32. +func (v *Value) MustInt32Slice() []int32 { + return v.data.([]int32) +} + +// IsInt32 gets whether the object contained is a int32 or not. +func (v *Value) IsInt32() bool { + _, ok := v.data.(int32) + return ok +} + +// IsInt32Slice gets whether the object contained is a []int32 or not. +func (v *Value) IsInt32Slice() bool { + _, ok := v.data.([]int32) + return ok +} + +// EachInt32 calls the specified callback for each object +// in the []int32. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt32(callback func(int, int32) bool) *Value { + + for index, val := range v.MustInt32Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt32 uses the specified decider function to select items +// from the []int32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt32(decider func(int, int32) bool) *Value { + + var selected []int32 + + v.EachInt32(func(index int, val int32) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int32. +func (v *Value) GroupInt32(grouper func(int, int32) string) *Value { + + groups := make(map[string][]int32) + + v.EachInt32(func(index int, val int32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt32 uses the specified function to replace each int32s +// by iterating each item. The data in the returned result will be a +// []int32 containing the replaced items. +func (v *Value) ReplaceInt32(replacer func(int, int32) int32) *Value { + + arr := v.MustInt32Slice() + replaced := make([]int32, len(arr)) + + v.EachInt32(func(index int, val int32) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt32 uses the specified collector function to collect a value +// for each of the int32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt32(collector func(int, int32) interface{}) *Value { + + arr := v.MustInt32Slice() + collected := make([]interface{}, len(arr)) + + v.EachInt32(func(index int, val int32) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Int64 (int64 and []int64) + -------------------------------------------------- +*/ + +// Int64 gets the value as a int64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int64(optionalDefault ...int64) int64 { + if s, ok := v.data.(int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt64 gets the value as a int64. +// +// Panics if the object is not a int64. +func (v *Value) MustInt64() int64 { + return v.data.(int64) +} + +// Int64Slice gets the value as a []int64, returns the optionalDefault +// value or nil if the value is not a []int64. +func (v *Value) Int64Slice(optionalDefault ...[]int64) []int64 { + if s, ok := v.data.([]int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt64Slice gets the value as a []int64. +// +// Panics if the object is not a []int64. +func (v *Value) MustInt64Slice() []int64 { + return v.data.([]int64) +} + +// IsInt64 gets whether the object contained is a int64 or not. +func (v *Value) IsInt64() bool { + _, ok := v.data.(int64) + return ok +} + +// IsInt64Slice gets whether the object contained is a []int64 or not. +func (v *Value) IsInt64Slice() bool { + _, ok := v.data.([]int64) + return ok +} + +// EachInt64 calls the specified callback for each object +// in the []int64. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt64(callback func(int, int64) bool) *Value { + + for index, val := range v.MustInt64Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereInt64 uses the specified decider function to select items +// from the []int64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt64(decider func(int, int64) bool) *Value { + + var selected []int64 + + v.EachInt64(func(index int, val int64) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupInt64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int64. +func (v *Value) GroupInt64(grouper func(int, int64) string) *Value { + + groups := make(map[string][]int64) + + v.EachInt64(func(index int, val int64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceInt64 uses the specified function to replace each int64s +// by iterating each item. The data in the returned result will be a +// []int64 containing the replaced items. +func (v *Value) ReplaceInt64(replacer func(int, int64) int64) *Value { + + arr := v.MustInt64Slice() + replaced := make([]int64, len(arr)) + + v.EachInt64(func(index int, val int64) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectInt64 uses the specified collector function to collect a value +// for each of the int64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt64(collector func(int, int64) interface{}) *Value { + + arr := v.MustInt64Slice() + collected := make([]interface{}, len(arr)) + + v.EachInt64(func(index int, val int64) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint (uint and []uint) + -------------------------------------------------- +*/ + +// Uint gets the value as a uint, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint(optionalDefault ...uint) uint { + if s, ok := v.data.(uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint gets the value as a uint. +// +// Panics if the object is not a uint. +func (v *Value) MustUint() uint { + return v.data.(uint) +} + +// UintSlice gets the value as a []uint, returns the optionalDefault +// value or nil if the value is not a []uint. +func (v *Value) UintSlice(optionalDefault ...[]uint) []uint { + if s, ok := v.data.([]uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintSlice gets the value as a []uint. +// +// Panics if the object is not a []uint. +func (v *Value) MustUintSlice() []uint { + return v.data.([]uint) +} + +// IsUint gets whether the object contained is a uint or not. +func (v *Value) IsUint() bool { + _, ok := v.data.(uint) + return ok +} + +// IsUintSlice gets whether the object contained is a []uint or not. +func (v *Value) IsUintSlice() bool { + _, ok := v.data.([]uint) + return ok +} + +// EachUint calls the specified callback for each object +// in the []uint. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint(callback func(int, uint) bool) *Value { + + for index, val := range v.MustUintSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint uses the specified decider function to select items +// from the []uint. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint(decider func(int, uint) bool) *Value { + + var selected []uint + + v.EachUint(func(index int, val uint) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint. +func (v *Value) GroupUint(grouper func(int, uint) string) *Value { + + groups := make(map[string][]uint) + + v.EachUint(func(index int, val uint) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint uses the specified function to replace each uints +// by iterating each item. The data in the returned result will be a +// []uint containing the replaced items. +func (v *Value) ReplaceUint(replacer func(int, uint) uint) *Value { + + arr := v.MustUintSlice() + replaced := make([]uint, len(arr)) + + v.EachUint(func(index int, val uint) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint uses the specified collector function to collect a value +// for each of the uints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint(collector func(int, uint) interface{}) *Value { + + arr := v.MustUintSlice() + collected := make([]interface{}, len(arr)) + + v.EachUint(func(index int, val uint) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint8 (uint8 and []uint8) + -------------------------------------------------- +*/ + +// Uint8 gets the value as a uint8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint8(optionalDefault ...uint8) uint8 { + if s, ok := v.data.(uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint8 gets the value as a uint8. +// +// Panics if the object is not a uint8. +func (v *Value) MustUint8() uint8 { + return v.data.(uint8) +} + +// Uint8Slice gets the value as a []uint8, returns the optionalDefault +// value or nil if the value is not a []uint8. +func (v *Value) Uint8Slice(optionalDefault ...[]uint8) []uint8 { + if s, ok := v.data.([]uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint8Slice gets the value as a []uint8. +// +// Panics if the object is not a []uint8. +func (v *Value) MustUint8Slice() []uint8 { + return v.data.([]uint8) +} + +// IsUint8 gets whether the object contained is a uint8 or not. +func (v *Value) IsUint8() bool { + _, ok := v.data.(uint8) + return ok +} + +// IsUint8Slice gets whether the object contained is a []uint8 or not. +func (v *Value) IsUint8Slice() bool { + _, ok := v.data.([]uint8) + return ok +} + +// EachUint8 calls the specified callback for each object +// in the []uint8. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint8(callback func(int, uint8) bool) *Value { + + for index, val := range v.MustUint8Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint8 uses the specified decider function to select items +// from the []uint8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint8(decider func(int, uint8) bool) *Value { + + var selected []uint8 + + v.EachUint8(func(index int, val uint8) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint8. +func (v *Value) GroupUint8(grouper func(int, uint8) string) *Value { + + groups := make(map[string][]uint8) + + v.EachUint8(func(index int, val uint8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint8 uses the specified function to replace each uint8s +// by iterating each item. The data in the returned result will be a +// []uint8 containing the replaced items. +func (v *Value) ReplaceUint8(replacer func(int, uint8) uint8) *Value { + + arr := v.MustUint8Slice() + replaced := make([]uint8, len(arr)) + + v.EachUint8(func(index int, val uint8) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint8 uses the specified collector function to collect a value +// for each of the uint8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint8(collector func(int, uint8) interface{}) *Value { + + arr := v.MustUint8Slice() + collected := make([]interface{}, len(arr)) + + v.EachUint8(func(index int, val uint8) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint16 (uint16 and []uint16) + -------------------------------------------------- +*/ + +// Uint16 gets the value as a uint16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint16(optionalDefault ...uint16) uint16 { + if s, ok := v.data.(uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint16 gets the value as a uint16. +// +// Panics if the object is not a uint16. +func (v *Value) MustUint16() uint16 { + return v.data.(uint16) +} + +// Uint16Slice gets the value as a []uint16, returns the optionalDefault +// value or nil if the value is not a []uint16. +func (v *Value) Uint16Slice(optionalDefault ...[]uint16) []uint16 { + if s, ok := v.data.([]uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint16Slice gets the value as a []uint16. +// +// Panics if the object is not a []uint16. +func (v *Value) MustUint16Slice() []uint16 { + return v.data.([]uint16) +} + +// IsUint16 gets whether the object contained is a uint16 or not. +func (v *Value) IsUint16() bool { + _, ok := v.data.(uint16) + return ok +} + +// IsUint16Slice gets whether the object contained is a []uint16 or not. +func (v *Value) IsUint16Slice() bool { + _, ok := v.data.([]uint16) + return ok +} + +// EachUint16 calls the specified callback for each object +// in the []uint16. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint16(callback func(int, uint16) bool) *Value { + + for index, val := range v.MustUint16Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint16 uses the specified decider function to select items +// from the []uint16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint16(decider func(int, uint16) bool) *Value { + + var selected []uint16 + + v.EachUint16(func(index int, val uint16) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint16. +func (v *Value) GroupUint16(grouper func(int, uint16) string) *Value { + + groups := make(map[string][]uint16) + + v.EachUint16(func(index int, val uint16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint16 uses the specified function to replace each uint16s +// by iterating each item. The data in the returned result will be a +// []uint16 containing the replaced items. +func (v *Value) ReplaceUint16(replacer func(int, uint16) uint16) *Value { + + arr := v.MustUint16Slice() + replaced := make([]uint16, len(arr)) + + v.EachUint16(func(index int, val uint16) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint16 uses the specified collector function to collect a value +// for each of the uint16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint16(collector func(int, uint16) interface{}) *Value { + + arr := v.MustUint16Slice() + collected := make([]interface{}, len(arr)) + + v.EachUint16(func(index int, val uint16) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint32 (uint32 and []uint32) + -------------------------------------------------- +*/ + +// Uint32 gets the value as a uint32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint32(optionalDefault ...uint32) uint32 { + if s, ok := v.data.(uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint32 gets the value as a uint32. +// +// Panics if the object is not a uint32. +func (v *Value) MustUint32() uint32 { + return v.data.(uint32) +} + +// Uint32Slice gets the value as a []uint32, returns the optionalDefault +// value or nil if the value is not a []uint32. +func (v *Value) Uint32Slice(optionalDefault ...[]uint32) []uint32 { + if s, ok := v.data.([]uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint32Slice gets the value as a []uint32. +// +// Panics if the object is not a []uint32. +func (v *Value) MustUint32Slice() []uint32 { + return v.data.([]uint32) +} + +// IsUint32 gets whether the object contained is a uint32 or not. +func (v *Value) IsUint32() bool { + _, ok := v.data.(uint32) + return ok +} + +// IsUint32Slice gets whether the object contained is a []uint32 or not. +func (v *Value) IsUint32Slice() bool { + _, ok := v.data.([]uint32) + return ok +} + +// EachUint32 calls the specified callback for each object +// in the []uint32. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint32(callback func(int, uint32) bool) *Value { + + for index, val := range v.MustUint32Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint32 uses the specified decider function to select items +// from the []uint32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint32(decider func(int, uint32) bool) *Value { + + var selected []uint32 + + v.EachUint32(func(index int, val uint32) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint32. +func (v *Value) GroupUint32(grouper func(int, uint32) string) *Value { + + groups := make(map[string][]uint32) + + v.EachUint32(func(index int, val uint32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint32 uses the specified function to replace each uint32s +// by iterating each item. The data in the returned result will be a +// []uint32 containing the replaced items. +func (v *Value) ReplaceUint32(replacer func(int, uint32) uint32) *Value { + + arr := v.MustUint32Slice() + replaced := make([]uint32, len(arr)) + + v.EachUint32(func(index int, val uint32) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint32 uses the specified collector function to collect a value +// for each of the uint32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint32(collector func(int, uint32) interface{}) *Value { + + arr := v.MustUint32Slice() + collected := make([]interface{}, len(arr)) + + v.EachUint32(func(index int, val uint32) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uint64 (uint64 and []uint64) + -------------------------------------------------- +*/ + +// Uint64 gets the value as a uint64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint64(optionalDefault ...uint64) uint64 { + if s, ok := v.data.(uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint64 gets the value as a uint64. +// +// Panics if the object is not a uint64. +func (v *Value) MustUint64() uint64 { + return v.data.(uint64) +} + +// Uint64Slice gets the value as a []uint64, returns the optionalDefault +// value or nil if the value is not a []uint64. +func (v *Value) Uint64Slice(optionalDefault ...[]uint64) []uint64 { + if s, ok := v.data.([]uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint64Slice gets the value as a []uint64. +// +// Panics if the object is not a []uint64. +func (v *Value) MustUint64Slice() []uint64 { + return v.data.([]uint64) +} + +// IsUint64 gets whether the object contained is a uint64 or not. +func (v *Value) IsUint64() bool { + _, ok := v.data.(uint64) + return ok +} + +// IsUint64Slice gets whether the object contained is a []uint64 or not. +func (v *Value) IsUint64Slice() bool { + _, ok := v.data.([]uint64) + return ok +} + +// EachUint64 calls the specified callback for each object +// in the []uint64. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint64(callback func(int, uint64) bool) *Value { + + for index, val := range v.MustUint64Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUint64 uses the specified decider function to select items +// from the []uint64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint64(decider func(int, uint64) bool) *Value { + + var selected []uint64 + + v.EachUint64(func(index int, val uint64) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUint64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint64. +func (v *Value) GroupUint64(grouper func(int, uint64) string) *Value { + + groups := make(map[string][]uint64) + + v.EachUint64(func(index int, val uint64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUint64 uses the specified function to replace each uint64s +// by iterating each item. The data in the returned result will be a +// []uint64 containing the replaced items. +func (v *Value) ReplaceUint64(replacer func(int, uint64) uint64) *Value { + + arr := v.MustUint64Slice() + replaced := make([]uint64, len(arr)) + + v.EachUint64(func(index int, val uint64) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUint64 uses the specified collector function to collect a value +// for each of the uint64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint64(collector func(int, uint64) interface{}) *Value { + + arr := v.MustUint64Slice() + collected := make([]interface{}, len(arr)) + + v.EachUint64(func(index int, val uint64) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Uintptr (uintptr and []uintptr) + -------------------------------------------------- +*/ + +// Uintptr gets the value as a uintptr, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uintptr(optionalDefault ...uintptr) uintptr { + if s, ok := v.data.(uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUintptr gets the value as a uintptr. +// +// Panics if the object is not a uintptr. +func (v *Value) MustUintptr() uintptr { + return v.data.(uintptr) +} + +// UintptrSlice gets the value as a []uintptr, returns the optionalDefault +// value or nil if the value is not a []uintptr. +func (v *Value) UintptrSlice(optionalDefault ...[]uintptr) []uintptr { + if s, ok := v.data.([]uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintptrSlice gets the value as a []uintptr. +// +// Panics if the object is not a []uintptr. +func (v *Value) MustUintptrSlice() []uintptr { + return v.data.([]uintptr) +} + +// IsUintptr gets whether the object contained is a uintptr or not. +func (v *Value) IsUintptr() bool { + _, ok := v.data.(uintptr) + return ok +} + +// IsUintptrSlice gets whether the object contained is a []uintptr or not. +func (v *Value) IsUintptrSlice() bool { + _, ok := v.data.([]uintptr) + return ok +} + +// EachUintptr calls the specified callback for each object +// in the []uintptr. +// +// Panics if the object is the wrong type. +func (v *Value) EachUintptr(callback func(int, uintptr) bool) *Value { + + for index, val := range v.MustUintptrSlice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereUintptr uses the specified decider function to select items +// from the []uintptr. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUintptr(decider func(int, uintptr) bool) *Value { + + var selected []uintptr + + v.EachUintptr(func(index int, val uintptr) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupUintptr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uintptr. +func (v *Value) GroupUintptr(grouper func(int, uintptr) string) *Value { + + groups := make(map[string][]uintptr) + + v.EachUintptr(func(index int, val uintptr) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uintptr, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceUintptr uses the specified function to replace each uintptrs +// by iterating each item. The data in the returned result will be a +// []uintptr containing the replaced items. +func (v *Value) ReplaceUintptr(replacer func(int, uintptr) uintptr) *Value { + + arr := v.MustUintptrSlice() + replaced := make([]uintptr, len(arr)) + + v.EachUintptr(func(index int, val uintptr) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectUintptr uses the specified collector function to collect a value +// for each of the uintptrs in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUintptr(collector func(int, uintptr) interface{}) *Value { + + arr := v.MustUintptrSlice() + collected := make([]interface{}, len(arr)) + + v.EachUintptr(func(index int, val uintptr) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Float32 (float32 and []float32) + -------------------------------------------------- +*/ + +// Float32 gets the value as a float32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float32(optionalDefault ...float32) float32 { + if s, ok := v.data.(float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat32 gets the value as a float32. +// +// Panics if the object is not a float32. +func (v *Value) MustFloat32() float32 { + return v.data.(float32) +} + +// Float32Slice gets the value as a []float32, returns the optionalDefault +// value or nil if the value is not a []float32. +func (v *Value) Float32Slice(optionalDefault ...[]float32) []float32 { + if s, ok := v.data.([]float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat32Slice gets the value as a []float32. +// +// Panics if the object is not a []float32. +func (v *Value) MustFloat32Slice() []float32 { + return v.data.([]float32) +} + +// IsFloat32 gets whether the object contained is a float32 or not. +func (v *Value) IsFloat32() bool { + _, ok := v.data.(float32) + return ok +} + +// IsFloat32Slice gets whether the object contained is a []float32 or not. +func (v *Value) IsFloat32Slice() bool { + _, ok := v.data.([]float32) + return ok +} + +// EachFloat32 calls the specified callback for each object +// in the []float32. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat32(callback func(int, float32) bool) *Value { + + for index, val := range v.MustFloat32Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereFloat32 uses the specified decider function to select items +// from the []float32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat32(decider func(int, float32) bool) *Value { + + var selected []float32 + + v.EachFloat32(func(index int, val float32) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupFloat32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float32. +func (v *Value) GroupFloat32(grouper func(int, float32) string) *Value { + + groups := make(map[string][]float32) + + v.EachFloat32(func(index int, val float32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceFloat32 uses the specified function to replace each float32s +// by iterating each item. The data in the returned result will be a +// []float32 containing the replaced items. +func (v *Value) ReplaceFloat32(replacer func(int, float32) float32) *Value { + + arr := v.MustFloat32Slice() + replaced := make([]float32, len(arr)) + + v.EachFloat32(func(index int, val float32) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectFloat32 uses the specified collector function to collect a value +// for each of the float32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat32(collector func(int, float32) interface{}) *Value { + + arr := v.MustFloat32Slice() + collected := make([]interface{}, len(arr)) + + v.EachFloat32(func(index int, val float32) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Float64 (float64 and []float64) + -------------------------------------------------- +*/ + +// Float64 gets the value as a float64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float64(optionalDefault ...float64) float64 { + if s, ok := v.data.(float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat64 gets the value as a float64. +// +// Panics if the object is not a float64. +func (v *Value) MustFloat64() float64 { + return v.data.(float64) +} + +// Float64Slice gets the value as a []float64, returns the optionalDefault +// value or nil if the value is not a []float64. +func (v *Value) Float64Slice(optionalDefault ...[]float64) []float64 { + if s, ok := v.data.([]float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat64Slice gets the value as a []float64. +// +// Panics if the object is not a []float64. +func (v *Value) MustFloat64Slice() []float64 { + return v.data.([]float64) +} + +// IsFloat64 gets whether the object contained is a float64 or not. +func (v *Value) IsFloat64() bool { + _, ok := v.data.(float64) + return ok +} + +// IsFloat64Slice gets whether the object contained is a []float64 or not. +func (v *Value) IsFloat64Slice() bool { + _, ok := v.data.([]float64) + return ok +} + +// EachFloat64 calls the specified callback for each object +// in the []float64. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat64(callback func(int, float64) bool) *Value { + + for index, val := range v.MustFloat64Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereFloat64 uses the specified decider function to select items +// from the []float64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat64(decider func(int, float64) bool) *Value { + + var selected []float64 + + v.EachFloat64(func(index int, val float64) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupFloat64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float64. +func (v *Value) GroupFloat64(grouper func(int, float64) string) *Value { + + groups := make(map[string][]float64) + + v.EachFloat64(func(index int, val float64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceFloat64 uses the specified function to replace each float64s +// by iterating each item. The data in the returned result will be a +// []float64 containing the replaced items. +func (v *Value) ReplaceFloat64(replacer func(int, float64) float64) *Value { + + arr := v.MustFloat64Slice() + replaced := make([]float64, len(arr)) + + v.EachFloat64(func(index int, val float64) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectFloat64 uses the specified collector function to collect a value +// for each of the float64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat64(collector func(int, float64) interface{}) *Value { + + arr := v.MustFloat64Slice() + collected := make([]interface{}, len(arr)) + + v.EachFloat64(func(index int, val float64) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Complex64 (complex64 and []complex64) + -------------------------------------------------- +*/ + +// Complex64 gets the value as a complex64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex64(optionalDefault ...complex64) complex64 { + if s, ok := v.data.(complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex64 gets the value as a complex64. +// +// Panics if the object is not a complex64. +func (v *Value) MustComplex64() complex64 { + return v.data.(complex64) +} + +// Complex64Slice gets the value as a []complex64, returns the optionalDefault +// value or nil if the value is not a []complex64. +func (v *Value) Complex64Slice(optionalDefault ...[]complex64) []complex64 { + if s, ok := v.data.([]complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex64Slice gets the value as a []complex64. +// +// Panics if the object is not a []complex64. +func (v *Value) MustComplex64Slice() []complex64 { + return v.data.([]complex64) +} + +// IsComplex64 gets whether the object contained is a complex64 or not. +func (v *Value) IsComplex64() bool { + _, ok := v.data.(complex64) + return ok +} + +// IsComplex64Slice gets whether the object contained is a []complex64 or not. +func (v *Value) IsComplex64Slice() bool { + _, ok := v.data.([]complex64) + return ok +} + +// EachComplex64 calls the specified callback for each object +// in the []complex64. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex64(callback func(int, complex64) bool) *Value { + + for index, val := range v.MustComplex64Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereComplex64 uses the specified decider function to select items +// from the []complex64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex64(decider func(int, complex64) bool) *Value { + + var selected []complex64 + + v.EachComplex64(func(index int, val complex64) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupComplex64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex64. +func (v *Value) GroupComplex64(grouper func(int, complex64) string) *Value { + + groups := make(map[string][]complex64) + + v.EachComplex64(func(index int, val complex64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceComplex64 uses the specified function to replace each complex64s +// by iterating each item. The data in the returned result will be a +// []complex64 containing the replaced items. +func (v *Value) ReplaceComplex64(replacer func(int, complex64) complex64) *Value { + + arr := v.MustComplex64Slice() + replaced := make([]complex64, len(arr)) + + v.EachComplex64(func(index int, val complex64) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectComplex64 uses the specified collector function to collect a value +// for each of the complex64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex64(collector func(int, complex64) interface{}) *Value { + + arr := v.MustComplex64Slice() + collected := make([]interface{}, len(arr)) + + v.EachComplex64(func(index int, val complex64) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} + +/* + Complex128 (complex128 and []complex128) + -------------------------------------------------- +*/ + +// Complex128 gets the value as a complex128, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex128(optionalDefault ...complex128) complex128 { + if s, ok := v.data.(complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex128 gets the value as a complex128. +// +// Panics if the object is not a complex128. +func (v *Value) MustComplex128() complex128 { + return v.data.(complex128) +} + +// Complex128Slice gets the value as a []complex128, returns the optionalDefault +// value or nil if the value is not a []complex128. +func (v *Value) Complex128Slice(optionalDefault ...[]complex128) []complex128 { + if s, ok := v.data.([]complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex128Slice gets the value as a []complex128. +// +// Panics if the object is not a []complex128. +func (v *Value) MustComplex128Slice() []complex128 { + return v.data.([]complex128) +} + +// IsComplex128 gets whether the object contained is a complex128 or not. +func (v *Value) IsComplex128() bool { + _, ok := v.data.(complex128) + return ok +} + +// IsComplex128Slice gets whether the object contained is a []complex128 or not. +func (v *Value) IsComplex128Slice() bool { + _, ok := v.data.([]complex128) + return ok +} + +// EachComplex128 calls the specified callback for each object +// in the []complex128. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex128(callback func(int, complex128) bool) *Value { + + for index, val := range v.MustComplex128Slice() { + carryon := callback(index, val) + if carryon == false { + break + } + } + + return v + +} + +// WhereComplex128 uses the specified decider function to select items +// from the []complex128. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex128(decider func(int, complex128) bool) *Value { + + var selected []complex128 + + v.EachComplex128(func(index int, val complex128) bool { + shouldSelect := decider(index, val) + if shouldSelect == false { + selected = append(selected, val) + } + return true + }) + + return &Value{data: selected} + +} + +// GroupComplex128 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex128. +func (v *Value) GroupComplex128(grouper func(int, complex128) string) *Value { + + groups := make(map[string][]complex128) + + v.EachComplex128(func(index int, val complex128) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex128, 0) + } + groups[group] = append(groups[group], val) + return true + }) + + return &Value{data: groups} + +} + +// ReplaceComplex128 uses the specified function to replace each complex128s +// by iterating each item. The data in the returned result will be a +// []complex128 containing the replaced items. +func (v *Value) ReplaceComplex128(replacer func(int, complex128) complex128) *Value { + + arr := v.MustComplex128Slice() + replaced := make([]complex128, len(arr)) + + v.EachComplex128(func(index int, val complex128) bool { + replaced[index] = replacer(index, val) + return true + }) + + return &Value{data: replaced} + +} + +// CollectComplex128 uses the specified collector function to collect a value +// for each of the complex128s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex128(collector func(int, complex128) interface{}) *Value { + + arr := v.MustComplex128Slice() + collected := make([]interface{}, len(arr)) + + v.EachComplex128(func(index int, val complex128) bool { + collected[index] = collector(index, val) + return true + }) + + return &Value{data: collected} +} diff --git a/third_party/src/github.com/stretchr/objx/type_specific_codegen_test.go b/third_party/src/github.com/stretchr/objx/type_specific_codegen_test.go new file mode 100644 index 00000000000..f7a4fceea3b --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/type_specific_codegen_test.go @@ -0,0 +1,2867 @@ +package objx + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestInter(t *testing.T) { + + val := interface{}("something") + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Inter()) + assert.Equal(t, val, New(m).Get("value").MustInter()) + assert.Equal(t, interface{}(nil), New(m).Get("nothing").Inter()) + assert.Equal(t, val, New(m).Get("nothing").Inter("something")) + + assert.Panics(t, func() { + New(m).Get("age").MustInter() + }) + +} + +func TestInterSlice(t *testing.T) { + + val := interface{}("something") + m := map[string]interface{}{"value": []interface{}{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").InterSlice()[0]) + assert.Equal(t, val, New(m).Get("value").MustInterSlice()[0]) + assert.Equal(t, []interface{}(nil), New(m).Get("nothing").InterSlice()) + assert.Equal(t, val, New(m).Get("nothing").InterSlice([]interface{}{interface{}("something")})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustInterSlice() + }) + +} + +func TestIsInter(t *testing.T) { + + var v *Value + + v = &Value{data: interface{}("something")} + assert.True(t, v.IsInter()) + + v = &Value{data: []interface{}{interface{}("something")}} + assert.True(t, v.IsInterSlice()) + +} + +func TestEachInter(t *testing.T) { + + v := &Value{data: []interface{}{interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something")}} + count := 0 + replacedVals := make([]interface{}, 0) + assert.Equal(t, v, v.EachInter(func(i int, val interface{}) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustInterSlice()[0]) + assert.Equal(t, replacedVals[1], v.MustInterSlice()[1]) + assert.Equal(t, replacedVals[2], v.MustInterSlice()[2]) + +} + +func TestWhereInter(t *testing.T) { + + v := &Value{data: []interface{}{interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something")}} + + selected := v.WhereInter(func(i int, val interface{}) bool { + return i%2 == 0 + }).MustInterSlice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupInter(t *testing.T) { + + v := &Value{data: []interface{}{interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something")}} + + grouped := v.GroupInter(func(i int, val interface{}) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]interface{}) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceInter(t *testing.T) { + + v := &Value{data: []interface{}{interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something")}} + + rawArr := v.MustInterSlice() + + replaced := v.ReplaceInter(func(index int, val interface{}) interface{} { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustInterSlice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectInter(t *testing.T) { + + v := &Value{data: []interface{}{interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something"), interface{}("something")}} + + collected := v.CollectInter(func(index int, val interface{}) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestMSI(t *testing.T) { + + val := map[string]interface{}(map[string]interface{}{"name": "Tyler"}) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").MSI()) + assert.Equal(t, val, New(m).Get("value").MustMSI()) + assert.Equal(t, map[string]interface{}(nil), New(m).Get("nothing").MSI()) + assert.Equal(t, val, New(m).Get("nothing").MSI(map[string]interface{}{"name": "Tyler"})) + + assert.Panics(t, func() { + New(m).Get("age").MustMSI() + }) + +} + +func TestMSISlice(t *testing.T) { + + val := map[string]interface{}(map[string]interface{}{"name": "Tyler"}) + m := map[string]interface{}{"value": []map[string]interface{}{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").MSISlice()[0]) + assert.Equal(t, val, New(m).Get("value").MustMSISlice()[0]) + assert.Equal(t, []map[string]interface{}(nil), New(m).Get("nothing").MSISlice()) + assert.Equal(t, val, New(m).Get("nothing").MSISlice([]map[string]interface{}{map[string]interface{}(map[string]interface{}{"name": "Tyler"})})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustMSISlice() + }) + +} + +func TestIsMSI(t *testing.T) { + + var v *Value + + v = &Value{data: map[string]interface{}(map[string]interface{}{"name": "Tyler"})} + assert.True(t, v.IsMSI()) + + v = &Value{data: []map[string]interface{}{map[string]interface{}(map[string]interface{}{"name": "Tyler"})}} + assert.True(t, v.IsMSISlice()) + +} + +func TestEachMSI(t *testing.T) { + + v := &Value{data: []map[string]interface{}{map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"})}} + count := 0 + replacedVals := make([]map[string]interface{}, 0) + assert.Equal(t, v, v.EachMSI(func(i int, val map[string]interface{}) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustMSISlice()[0]) + assert.Equal(t, replacedVals[1], v.MustMSISlice()[1]) + assert.Equal(t, replacedVals[2], v.MustMSISlice()[2]) + +} + +func TestWhereMSI(t *testing.T) { + + v := &Value{data: []map[string]interface{}{map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"})}} + + selected := v.WhereMSI(func(i int, val map[string]interface{}) bool { + return i%2 == 0 + }).MustMSISlice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupMSI(t *testing.T) { + + v := &Value{data: []map[string]interface{}{map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"})}} + + grouped := v.GroupMSI(func(i int, val map[string]interface{}) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]map[string]interface{}) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceMSI(t *testing.T) { + + v := &Value{data: []map[string]interface{}{map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"})}} + + rawArr := v.MustMSISlice() + + replaced := v.ReplaceMSI(func(index int, val map[string]interface{}) map[string]interface{} { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustMSISlice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectMSI(t *testing.T) { + + v := &Value{data: []map[string]interface{}{map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"}), map[string]interface{}(map[string]interface{}{"name": "Tyler"})}} + + collected := v.CollectMSI(func(index int, val map[string]interface{}) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestObjxMap(t *testing.T) { + + val := (Map)(New(1)) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").ObjxMap()) + assert.Equal(t, val, New(m).Get("value").MustObjxMap()) + assert.Equal(t, (Map)(New(nil)), New(m).Get("nothing").ObjxMap()) + assert.Equal(t, val, New(m).Get("nothing").ObjxMap(New(1))) + + assert.Panics(t, func() { + New(m).Get("age").MustObjxMap() + }) + +} + +func TestObjxMapSlice(t *testing.T) { + + val := (Map)(New(1)) + m := map[string]interface{}{"value": [](Map){val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").ObjxMapSlice()[0]) + assert.Equal(t, val, New(m).Get("value").MustObjxMapSlice()[0]) + assert.Equal(t, [](Map)(nil), New(m).Get("nothing").ObjxMapSlice()) + assert.Equal(t, val, New(m).Get("nothing").ObjxMapSlice([](Map){(Map)(New(1))})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustObjxMapSlice() + }) + +} + +func TestIsObjxMap(t *testing.T) { + + var v *Value + + v = &Value{data: (Map)(New(1))} + assert.True(t, v.IsObjxMap()) + + v = &Value{data: [](Map){(Map)(New(1))}} + assert.True(t, v.IsObjxMapSlice()) + +} + +func TestEachObjxMap(t *testing.T) { + + v := &Value{data: [](Map){(Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1))}} + count := 0 + replacedVals := make([](Map), 0) + assert.Equal(t, v, v.EachObjxMap(func(i int, val Map) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustObjxMapSlice()[0]) + assert.Equal(t, replacedVals[1], v.MustObjxMapSlice()[1]) + assert.Equal(t, replacedVals[2], v.MustObjxMapSlice()[2]) + +} + +func TestWhereObjxMap(t *testing.T) { + + v := &Value{data: [](Map){(Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1))}} + + selected := v.WhereObjxMap(func(i int, val Map) bool { + return i%2 == 0 + }).MustObjxMapSlice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupObjxMap(t *testing.T) { + + v := &Value{data: [](Map){(Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1))}} + + grouped := v.GroupObjxMap(func(i int, val Map) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][](Map)) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceObjxMap(t *testing.T) { + + v := &Value{data: [](Map){(Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1))}} + + rawArr := v.MustObjxMapSlice() + + replaced := v.ReplaceObjxMap(func(index int, val Map) Map { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustObjxMapSlice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectObjxMap(t *testing.T) { + + v := &Value{data: [](Map){(Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1)), (Map)(New(1))}} + + collected := v.CollectObjxMap(func(index int, val Map) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestBool(t *testing.T) { + + val := bool(true) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Bool()) + assert.Equal(t, val, New(m).Get("value").MustBool()) + assert.Equal(t, bool(false), New(m).Get("nothing").Bool()) + assert.Equal(t, val, New(m).Get("nothing").Bool(true)) + + assert.Panics(t, func() { + New(m).Get("age").MustBool() + }) + +} + +func TestBoolSlice(t *testing.T) { + + val := bool(true) + m := map[string]interface{}{"value": []bool{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").BoolSlice()[0]) + assert.Equal(t, val, New(m).Get("value").MustBoolSlice()[0]) + assert.Equal(t, []bool(nil), New(m).Get("nothing").BoolSlice()) + assert.Equal(t, val, New(m).Get("nothing").BoolSlice([]bool{bool(true)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustBoolSlice() + }) + +} + +func TestIsBool(t *testing.T) { + + var v *Value + + v = &Value{data: bool(true)} + assert.True(t, v.IsBool()) + + v = &Value{data: []bool{bool(true)}} + assert.True(t, v.IsBoolSlice()) + +} + +func TestEachBool(t *testing.T) { + + v := &Value{data: []bool{bool(true), bool(true), bool(true), bool(true), bool(true)}} + count := 0 + replacedVals := make([]bool, 0) + assert.Equal(t, v, v.EachBool(func(i int, val bool) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustBoolSlice()[0]) + assert.Equal(t, replacedVals[1], v.MustBoolSlice()[1]) + assert.Equal(t, replacedVals[2], v.MustBoolSlice()[2]) + +} + +func TestWhereBool(t *testing.T) { + + v := &Value{data: []bool{bool(true), bool(true), bool(true), bool(true), bool(true), bool(true)}} + + selected := v.WhereBool(func(i int, val bool) bool { + return i%2 == 0 + }).MustBoolSlice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupBool(t *testing.T) { + + v := &Value{data: []bool{bool(true), bool(true), bool(true), bool(true), bool(true), bool(true)}} + + grouped := v.GroupBool(func(i int, val bool) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]bool) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceBool(t *testing.T) { + + v := &Value{data: []bool{bool(true), bool(true), bool(true), bool(true), bool(true), bool(true)}} + + rawArr := v.MustBoolSlice() + + replaced := v.ReplaceBool(func(index int, val bool) bool { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustBoolSlice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectBool(t *testing.T) { + + v := &Value{data: []bool{bool(true), bool(true), bool(true), bool(true), bool(true), bool(true)}} + + collected := v.CollectBool(func(index int, val bool) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestStr(t *testing.T) { + + val := string("hello") + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Str()) + assert.Equal(t, val, New(m).Get("value").MustStr()) + assert.Equal(t, string(""), New(m).Get("nothing").Str()) + assert.Equal(t, val, New(m).Get("nothing").Str("hello")) + + assert.Panics(t, func() { + New(m).Get("age").MustStr() + }) + +} + +func TestStrSlice(t *testing.T) { + + val := string("hello") + m := map[string]interface{}{"value": []string{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").StrSlice()[0]) + assert.Equal(t, val, New(m).Get("value").MustStrSlice()[0]) + assert.Equal(t, []string(nil), New(m).Get("nothing").StrSlice()) + assert.Equal(t, val, New(m).Get("nothing").StrSlice([]string{string("hello")})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustStrSlice() + }) + +} + +func TestIsStr(t *testing.T) { + + var v *Value + + v = &Value{data: string("hello")} + assert.True(t, v.IsStr()) + + v = &Value{data: []string{string("hello")}} + assert.True(t, v.IsStrSlice()) + +} + +func TestEachStr(t *testing.T) { + + v := &Value{data: []string{string("hello"), string("hello"), string("hello"), string("hello"), string("hello")}} + count := 0 + replacedVals := make([]string, 0) + assert.Equal(t, v, v.EachStr(func(i int, val string) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustStrSlice()[0]) + assert.Equal(t, replacedVals[1], v.MustStrSlice()[1]) + assert.Equal(t, replacedVals[2], v.MustStrSlice()[2]) + +} + +func TestWhereStr(t *testing.T) { + + v := &Value{data: []string{string("hello"), string("hello"), string("hello"), string("hello"), string("hello"), string("hello")}} + + selected := v.WhereStr(func(i int, val string) bool { + return i%2 == 0 + }).MustStrSlice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupStr(t *testing.T) { + + v := &Value{data: []string{string("hello"), string("hello"), string("hello"), string("hello"), string("hello"), string("hello")}} + + grouped := v.GroupStr(func(i int, val string) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]string) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceStr(t *testing.T) { + + v := &Value{data: []string{string("hello"), string("hello"), string("hello"), string("hello"), string("hello"), string("hello")}} + + rawArr := v.MustStrSlice() + + replaced := v.ReplaceStr(func(index int, val string) string { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustStrSlice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectStr(t *testing.T) { + + v := &Value{data: []string{string("hello"), string("hello"), string("hello"), string("hello"), string("hello"), string("hello")}} + + collected := v.CollectStr(func(index int, val string) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestInt(t *testing.T) { + + val := int(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Int()) + assert.Equal(t, val, New(m).Get("value").MustInt()) + assert.Equal(t, int(0), New(m).Get("nothing").Int()) + assert.Equal(t, val, New(m).Get("nothing").Int(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustInt() + }) + +} + +func TestIntSlice(t *testing.T) { + + val := int(1) + m := map[string]interface{}{"value": []int{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").IntSlice()[0]) + assert.Equal(t, val, New(m).Get("value").MustIntSlice()[0]) + assert.Equal(t, []int(nil), New(m).Get("nothing").IntSlice()) + assert.Equal(t, val, New(m).Get("nothing").IntSlice([]int{int(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustIntSlice() + }) + +} + +func TestIsInt(t *testing.T) { + + var v *Value + + v = &Value{data: int(1)} + assert.True(t, v.IsInt()) + + v = &Value{data: []int{int(1)}} + assert.True(t, v.IsIntSlice()) + +} + +func TestEachInt(t *testing.T) { + + v := &Value{data: []int{int(1), int(1), int(1), int(1), int(1)}} + count := 0 + replacedVals := make([]int, 0) + assert.Equal(t, v, v.EachInt(func(i int, val int) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustIntSlice()[0]) + assert.Equal(t, replacedVals[1], v.MustIntSlice()[1]) + assert.Equal(t, replacedVals[2], v.MustIntSlice()[2]) + +} + +func TestWhereInt(t *testing.T) { + + v := &Value{data: []int{int(1), int(1), int(1), int(1), int(1), int(1)}} + + selected := v.WhereInt(func(i int, val int) bool { + return i%2 == 0 + }).MustIntSlice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupInt(t *testing.T) { + + v := &Value{data: []int{int(1), int(1), int(1), int(1), int(1), int(1)}} + + grouped := v.GroupInt(func(i int, val int) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]int) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceInt(t *testing.T) { + + v := &Value{data: []int{int(1), int(1), int(1), int(1), int(1), int(1)}} + + rawArr := v.MustIntSlice() + + replaced := v.ReplaceInt(func(index int, val int) int { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustIntSlice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectInt(t *testing.T) { + + v := &Value{data: []int{int(1), int(1), int(1), int(1), int(1), int(1)}} + + collected := v.CollectInt(func(index int, val int) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestInt8(t *testing.T) { + + val := int8(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Int8()) + assert.Equal(t, val, New(m).Get("value").MustInt8()) + assert.Equal(t, int8(0), New(m).Get("nothing").Int8()) + assert.Equal(t, val, New(m).Get("nothing").Int8(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustInt8() + }) + +} + +func TestInt8Slice(t *testing.T) { + + val := int8(1) + m := map[string]interface{}{"value": []int8{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Int8Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustInt8Slice()[0]) + assert.Equal(t, []int8(nil), New(m).Get("nothing").Int8Slice()) + assert.Equal(t, val, New(m).Get("nothing").Int8Slice([]int8{int8(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustInt8Slice() + }) + +} + +func TestIsInt8(t *testing.T) { + + var v *Value + + v = &Value{data: int8(1)} + assert.True(t, v.IsInt8()) + + v = &Value{data: []int8{int8(1)}} + assert.True(t, v.IsInt8Slice()) + +} + +func TestEachInt8(t *testing.T) { + + v := &Value{data: []int8{int8(1), int8(1), int8(1), int8(1), int8(1)}} + count := 0 + replacedVals := make([]int8, 0) + assert.Equal(t, v, v.EachInt8(func(i int, val int8) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustInt8Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustInt8Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustInt8Slice()[2]) + +} + +func TestWhereInt8(t *testing.T) { + + v := &Value{data: []int8{int8(1), int8(1), int8(1), int8(1), int8(1), int8(1)}} + + selected := v.WhereInt8(func(i int, val int8) bool { + return i%2 == 0 + }).MustInt8Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupInt8(t *testing.T) { + + v := &Value{data: []int8{int8(1), int8(1), int8(1), int8(1), int8(1), int8(1)}} + + grouped := v.GroupInt8(func(i int, val int8) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]int8) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceInt8(t *testing.T) { + + v := &Value{data: []int8{int8(1), int8(1), int8(1), int8(1), int8(1), int8(1)}} + + rawArr := v.MustInt8Slice() + + replaced := v.ReplaceInt8(func(index int, val int8) int8 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustInt8Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectInt8(t *testing.T) { + + v := &Value{data: []int8{int8(1), int8(1), int8(1), int8(1), int8(1), int8(1)}} + + collected := v.CollectInt8(func(index int, val int8) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestInt16(t *testing.T) { + + val := int16(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Int16()) + assert.Equal(t, val, New(m).Get("value").MustInt16()) + assert.Equal(t, int16(0), New(m).Get("nothing").Int16()) + assert.Equal(t, val, New(m).Get("nothing").Int16(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustInt16() + }) + +} + +func TestInt16Slice(t *testing.T) { + + val := int16(1) + m := map[string]interface{}{"value": []int16{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Int16Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustInt16Slice()[0]) + assert.Equal(t, []int16(nil), New(m).Get("nothing").Int16Slice()) + assert.Equal(t, val, New(m).Get("nothing").Int16Slice([]int16{int16(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustInt16Slice() + }) + +} + +func TestIsInt16(t *testing.T) { + + var v *Value + + v = &Value{data: int16(1)} + assert.True(t, v.IsInt16()) + + v = &Value{data: []int16{int16(1)}} + assert.True(t, v.IsInt16Slice()) + +} + +func TestEachInt16(t *testing.T) { + + v := &Value{data: []int16{int16(1), int16(1), int16(1), int16(1), int16(1)}} + count := 0 + replacedVals := make([]int16, 0) + assert.Equal(t, v, v.EachInt16(func(i int, val int16) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustInt16Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustInt16Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustInt16Slice()[2]) + +} + +func TestWhereInt16(t *testing.T) { + + v := &Value{data: []int16{int16(1), int16(1), int16(1), int16(1), int16(1), int16(1)}} + + selected := v.WhereInt16(func(i int, val int16) bool { + return i%2 == 0 + }).MustInt16Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupInt16(t *testing.T) { + + v := &Value{data: []int16{int16(1), int16(1), int16(1), int16(1), int16(1), int16(1)}} + + grouped := v.GroupInt16(func(i int, val int16) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]int16) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceInt16(t *testing.T) { + + v := &Value{data: []int16{int16(1), int16(1), int16(1), int16(1), int16(1), int16(1)}} + + rawArr := v.MustInt16Slice() + + replaced := v.ReplaceInt16(func(index int, val int16) int16 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustInt16Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectInt16(t *testing.T) { + + v := &Value{data: []int16{int16(1), int16(1), int16(1), int16(1), int16(1), int16(1)}} + + collected := v.CollectInt16(func(index int, val int16) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestInt32(t *testing.T) { + + val := int32(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Int32()) + assert.Equal(t, val, New(m).Get("value").MustInt32()) + assert.Equal(t, int32(0), New(m).Get("nothing").Int32()) + assert.Equal(t, val, New(m).Get("nothing").Int32(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustInt32() + }) + +} + +func TestInt32Slice(t *testing.T) { + + val := int32(1) + m := map[string]interface{}{"value": []int32{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Int32Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustInt32Slice()[0]) + assert.Equal(t, []int32(nil), New(m).Get("nothing").Int32Slice()) + assert.Equal(t, val, New(m).Get("nothing").Int32Slice([]int32{int32(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustInt32Slice() + }) + +} + +func TestIsInt32(t *testing.T) { + + var v *Value + + v = &Value{data: int32(1)} + assert.True(t, v.IsInt32()) + + v = &Value{data: []int32{int32(1)}} + assert.True(t, v.IsInt32Slice()) + +} + +func TestEachInt32(t *testing.T) { + + v := &Value{data: []int32{int32(1), int32(1), int32(1), int32(1), int32(1)}} + count := 0 + replacedVals := make([]int32, 0) + assert.Equal(t, v, v.EachInt32(func(i int, val int32) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustInt32Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustInt32Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustInt32Slice()[2]) + +} + +func TestWhereInt32(t *testing.T) { + + v := &Value{data: []int32{int32(1), int32(1), int32(1), int32(1), int32(1), int32(1)}} + + selected := v.WhereInt32(func(i int, val int32) bool { + return i%2 == 0 + }).MustInt32Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupInt32(t *testing.T) { + + v := &Value{data: []int32{int32(1), int32(1), int32(1), int32(1), int32(1), int32(1)}} + + grouped := v.GroupInt32(func(i int, val int32) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]int32) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceInt32(t *testing.T) { + + v := &Value{data: []int32{int32(1), int32(1), int32(1), int32(1), int32(1), int32(1)}} + + rawArr := v.MustInt32Slice() + + replaced := v.ReplaceInt32(func(index int, val int32) int32 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustInt32Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectInt32(t *testing.T) { + + v := &Value{data: []int32{int32(1), int32(1), int32(1), int32(1), int32(1), int32(1)}} + + collected := v.CollectInt32(func(index int, val int32) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestInt64(t *testing.T) { + + val := int64(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Int64()) + assert.Equal(t, val, New(m).Get("value").MustInt64()) + assert.Equal(t, int64(0), New(m).Get("nothing").Int64()) + assert.Equal(t, val, New(m).Get("nothing").Int64(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustInt64() + }) + +} + +func TestInt64Slice(t *testing.T) { + + val := int64(1) + m := map[string]interface{}{"value": []int64{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Int64Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustInt64Slice()[0]) + assert.Equal(t, []int64(nil), New(m).Get("nothing").Int64Slice()) + assert.Equal(t, val, New(m).Get("nothing").Int64Slice([]int64{int64(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustInt64Slice() + }) + +} + +func TestIsInt64(t *testing.T) { + + var v *Value + + v = &Value{data: int64(1)} + assert.True(t, v.IsInt64()) + + v = &Value{data: []int64{int64(1)}} + assert.True(t, v.IsInt64Slice()) + +} + +func TestEachInt64(t *testing.T) { + + v := &Value{data: []int64{int64(1), int64(1), int64(1), int64(1), int64(1)}} + count := 0 + replacedVals := make([]int64, 0) + assert.Equal(t, v, v.EachInt64(func(i int, val int64) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustInt64Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustInt64Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustInt64Slice()[2]) + +} + +func TestWhereInt64(t *testing.T) { + + v := &Value{data: []int64{int64(1), int64(1), int64(1), int64(1), int64(1), int64(1)}} + + selected := v.WhereInt64(func(i int, val int64) bool { + return i%2 == 0 + }).MustInt64Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupInt64(t *testing.T) { + + v := &Value{data: []int64{int64(1), int64(1), int64(1), int64(1), int64(1), int64(1)}} + + grouped := v.GroupInt64(func(i int, val int64) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]int64) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceInt64(t *testing.T) { + + v := &Value{data: []int64{int64(1), int64(1), int64(1), int64(1), int64(1), int64(1)}} + + rawArr := v.MustInt64Slice() + + replaced := v.ReplaceInt64(func(index int, val int64) int64 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustInt64Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectInt64(t *testing.T) { + + v := &Value{data: []int64{int64(1), int64(1), int64(1), int64(1), int64(1), int64(1)}} + + collected := v.CollectInt64(func(index int, val int64) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestUint(t *testing.T) { + + val := uint(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uint()) + assert.Equal(t, val, New(m).Get("value").MustUint()) + assert.Equal(t, uint(0), New(m).Get("nothing").Uint()) + assert.Equal(t, val, New(m).Get("nothing").Uint(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustUint() + }) + +} + +func TestUintSlice(t *testing.T) { + + val := uint(1) + m := map[string]interface{}{"value": []uint{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").UintSlice()[0]) + assert.Equal(t, val, New(m).Get("value").MustUintSlice()[0]) + assert.Equal(t, []uint(nil), New(m).Get("nothing").UintSlice()) + assert.Equal(t, val, New(m).Get("nothing").UintSlice([]uint{uint(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustUintSlice() + }) + +} + +func TestIsUint(t *testing.T) { + + var v *Value + + v = &Value{data: uint(1)} + assert.True(t, v.IsUint()) + + v = &Value{data: []uint{uint(1)}} + assert.True(t, v.IsUintSlice()) + +} + +func TestEachUint(t *testing.T) { + + v := &Value{data: []uint{uint(1), uint(1), uint(1), uint(1), uint(1)}} + count := 0 + replacedVals := make([]uint, 0) + assert.Equal(t, v, v.EachUint(func(i int, val uint) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustUintSlice()[0]) + assert.Equal(t, replacedVals[1], v.MustUintSlice()[1]) + assert.Equal(t, replacedVals[2], v.MustUintSlice()[2]) + +} + +func TestWhereUint(t *testing.T) { + + v := &Value{data: []uint{uint(1), uint(1), uint(1), uint(1), uint(1), uint(1)}} + + selected := v.WhereUint(func(i int, val uint) bool { + return i%2 == 0 + }).MustUintSlice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupUint(t *testing.T) { + + v := &Value{data: []uint{uint(1), uint(1), uint(1), uint(1), uint(1), uint(1)}} + + grouped := v.GroupUint(func(i int, val uint) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]uint) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceUint(t *testing.T) { + + v := &Value{data: []uint{uint(1), uint(1), uint(1), uint(1), uint(1), uint(1)}} + + rawArr := v.MustUintSlice() + + replaced := v.ReplaceUint(func(index int, val uint) uint { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustUintSlice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectUint(t *testing.T) { + + v := &Value{data: []uint{uint(1), uint(1), uint(1), uint(1), uint(1), uint(1)}} + + collected := v.CollectUint(func(index int, val uint) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestUint8(t *testing.T) { + + val := uint8(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uint8()) + assert.Equal(t, val, New(m).Get("value").MustUint8()) + assert.Equal(t, uint8(0), New(m).Get("nothing").Uint8()) + assert.Equal(t, val, New(m).Get("nothing").Uint8(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustUint8() + }) + +} + +func TestUint8Slice(t *testing.T) { + + val := uint8(1) + m := map[string]interface{}{"value": []uint8{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uint8Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustUint8Slice()[0]) + assert.Equal(t, []uint8(nil), New(m).Get("nothing").Uint8Slice()) + assert.Equal(t, val, New(m).Get("nothing").Uint8Slice([]uint8{uint8(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustUint8Slice() + }) + +} + +func TestIsUint8(t *testing.T) { + + var v *Value + + v = &Value{data: uint8(1)} + assert.True(t, v.IsUint8()) + + v = &Value{data: []uint8{uint8(1)}} + assert.True(t, v.IsUint8Slice()) + +} + +func TestEachUint8(t *testing.T) { + + v := &Value{data: []uint8{uint8(1), uint8(1), uint8(1), uint8(1), uint8(1)}} + count := 0 + replacedVals := make([]uint8, 0) + assert.Equal(t, v, v.EachUint8(func(i int, val uint8) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustUint8Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustUint8Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustUint8Slice()[2]) + +} + +func TestWhereUint8(t *testing.T) { + + v := &Value{data: []uint8{uint8(1), uint8(1), uint8(1), uint8(1), uint8(1), uint8(1)}} + + selected := v.WhereUint8(func(i int, val uint8) bool { + return i%2 == 0 + }).MustUint8Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupUint8(t *testing.T) { + + v := &Value{data: []uint8{uint8(1), uint8(1), uint8(1), uint8(1), uint8(1), uint8(1)}} + + grouped := v.GroupUint8(func(i int, val uint8) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]uint8) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceUint8(t *testing.T) { + + v := &Value{data: []uint8{uint8(1), uint8(1), uint8(1), uint8(1), uint8(1), uint8(1)}} + + rawArr := v.MustUint8Slice() + + replaced := v.ReplaceUint8(func(index int, val uint8) uint8 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustUint8Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectUint8(t *testing.T) { + + v := &Value{data: []uint8{uint8(1), uint8(1), uint8(1), uint8(1), uint8(1), uint8(1)}} + + collected := v.CollectUint8(func(index int, val uint8) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestUint16(t *testing.T) { + + val := uint16(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uint16()) + assert.Equal(t, val, New(m).Get("value").MustUint16()) + assert.Equal(t, uint16(0), New(m).Get("nothing").Uint16()) + assert.Equal(t, val, New(m).Get("nothing").Uint16(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustUint16() + }) + +} + +func TestUint16Slice(t *testing.T) { + + val := uint16(1) + m := map[string]interface{}{"value": []uint16{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uint16Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustUint16Slice()[0]) + assert.Equal(t, []uint16(nil), New(m).Get("nothing").Uint16Slice()) + assert.Equal(t, val, New(m).Get("nothing").Uint16Slice([]uint16{uint16(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustUint16Slice() + }) + +} + +func TestIsUint16(t *testing.T) { + + var v *Value + + v = &Value{data: uint16(1)} + assert.True(t, v.IsUint16()) + + v = &Value{data: []uint16{uint16(1)}} + assert.True(t, v.IsUint16Slice()) + +} + +func TestEachUint16(t *testing.T) { + + v := &Value{data: []uint16{uint16(1), uint16(1), uint16(1), uint16(1), uint16(1)}} + count := 0 + replacedVals := make([]uint16, 0) + assert.Equal(t, v, v.EachUint16(func(i int, val uint16) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustUint16Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustUint16Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustUint16Slice()[2]) + +} + +func TestWhereUint16(t *testing.T) { + + v := &Value{data: []uint16{uint16(1), uint16(1), uint16(1), uint16(1), uint16(1), uint16(1)}} + + selected := v.WhereUint16(func(i int, val uint16) bool { + return i%2 == 0 + }).MustUint16Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupUint16(t *testing.T) { + + v := &Value{data: []uint16{uint16(1), uint16(1), uint16(1), uint16(1), uint16(1), uint16(1)}} + + grouped := v.GroupUint16(func(i int, val uint16) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]uint16) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceUint16(t *testing.T) { + + v := &Value{data: []uint16{uint16(1), uint16(1), uint16(1), uint16(1), uint16(1), uint16(1)}} + + rawArr := v.MustUint16Slice() + + replaced := v.ReplaceUint16(func(index int, val uint16) uint16 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustUint16Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectUint16(t *testing.T) { + + v := &Value{data: []uint16{uint16(1), uint16(1), uint16(1), uint16(1), uint16(1), uint16(1)}} + + collected := v.CollectUint16(func(index int, val uint16) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestUint32(t *testing.T) { + + val := uint32(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uint32()) + assert.Equal(t, val, New(m).Get("value").MustUint32()) + assert.Equal(t, uint32(0), New(m).Get("nothing").Uint32()) + assert.Equal(t, val, New(m).Get("nothing").Uint32(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustUint32() + }) + +} + +func TestUint32Slice(t *testing.T) { + + val := uint32(1) + m := map[string]interface{}{"value": []uint32{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uint32Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustUint32Slice()[0]) + assert.Equal(t, []uint32(nil), New(m).Get("nothing").Uint32Slice()) + assert.Equal(t, val, New(m).Get("nothing").Uint32Slice([]uint32{uint32(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustUint32Slice() + }) + +} + +func TestIsUint32(t *testing.T) { + + var v *Value + + v = &Value{data: uint32(1)} + assert.True(t, v.IsUint32()) + + v = &Value{data: []uint32{uint32(1)}} + assert.True(t, v.IsUint32Slice()) + +} + +func TestEachUint32(t *testing.T) { + + v := &Value{data: []uint32{uint32(1), uint32(1), uint32(1), uint32(1), uint32(1)}} + count := 0 + replacedVals := make([]uint32, 0) + assert.Equal(t, v, v.EachUint32(func(i int, val uint32) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustUint32Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustUint32Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustUint32Slice()[2]) + +} + +func TestWhereUint32(t *testing.T) { + + v := &Value{data: []uint32{uint32(1), uint32(1), uint32(1), uint32(1), uint32(1), uint32(1)}} + + selected := v.WhereUint32(func(i int, val uint32) bool { + return i%2 == 0 + }).MustUint32Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupUint32(t *testing.T) { + + v := &Value{data: []uint32{uint32(1), uint32(1), uint32(1), uint32(1), uint32(1), uint32(1)}} + + grouped := v.GroupUint32(func(i int, val uint32) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]uint32) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceUint32(t *testing.T) { + + v := &Value{data: []uint32{uint32(1), uint32(1), uint32(1), uint32(1), uint32(1), uint32(1)}} + + rawArr := v.MustUint32Slice() + + replaced := v.ReplaceUint32(func(index int, val uint32) uint32 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustUint32Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectUint32(t *testing.T) { + + v := &Value{data: []uint32{uint32(1), uint32(1), uint32(1), uint32(1), uint32(1), uint32(1)}} + + collected := v.CollectUint32(func(index int, val uint32) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestUint64(t *testing.T) { + + val := uint64(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uint64()) + assert.Equal(t, val, New(m).Get("value").MustUint64()) + assert.Equal(t, uint64(0), New(m).Get("nothing").Uint64()) + assert.Equal(t, val, New(m).Get("nothing").Uint64(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustUint64() + }) + +} + +func TestUint64Slice(t *testing.T) { + + val := uint64(1) + m := map[string]interface{}{"value": []uint64{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uint64Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustUint64Slice()[0]) + assert.Equal(t, []uint64(nil), New(m).Get("nothing").Uint64Slice()) + assert.Equal(t, val, New(m).Get("nothing").Uint64Slice([]uint64{uint64(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustUint64Slice() + }) + +} + +func TestIsUint64(t *testing.T) { + + var v *Value + + v = &Value{data: uint64(1)} + assert.True(t, v.IsUint64()) + + v = &Value{data: []uint64{uint64(1)}} + assert.True(t, v.IsUint64Slice()) + +} + +func TestEachUint64(t *testing.T) { + + v := &Value{data: []uint64{uint64(1), uint64(1), uint64(1), uint64(1), uint64(1)}} + count := 0 + replacedVals := make([]uint64, 0) + assert.Equal(t, v, v.EachUint64(func(i int, val uint64) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustUint64Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustUint64Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustUint64Slice()[2]) + +} + +func TestWhereUint64(t *testing.T) { + + v := &Value{data: []uint64{uint64(1), uint64(1), uint64(1), uint64(1), uint64(1), uint64(1)}} + + selected := v.WhereUint64(func(i int, val uint64) bool { + return i%2 == 0 + }).MustUint64Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupUint64(t *testing.T) { + + v := &Value{data: []uint64{uint64(1), uint64(1), uint64(1), uint64(1), uint64(1), uint64(1)}} + + grouped := v.GroupUint64(func(i int, val uint64) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]uint64) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceUint64(t *testing.T) { + + v := &Value{data: []uint64{uint64(1), uint64(1), uint64(1), uint64(1), uint64(1), uint64(1)}} + + rawArr := v.MustUint64Slice() + + replaced := v.ReplaceUint64(func(index int, val uint64) uint64 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustUint64Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectUint64(t *testing.T) { + + v := &Value{data: []uint64{uint64(1), uint64(1), uint64(1), uint64(1), uint64(1), uint64(1)}} + + collected := v.CollectUint64(func(index int, val uint64) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestUintptr(t *testing.T) { + + val := uintptr(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Uintptr()) + assert.Equal(t, val, New(m).Get("value").MustUintptr()) + assert.Equal(t, uintptr(0), New(m).Get("nothing").Uintptr()) + assert.Equal(t, val, New(m).Get("nothing").Uintptr(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustUintptr() + }) + +} + +func TestUintptrSlice(t *testing.T) { + + val := uintptr(1) + m := map[string]interface{}{"value": []uintptr{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").UintptrSlice()[0]) + assert.Equal(t, val, New(m).Get("value").MustUintptrSlice()[0]) + assert.Equal(t, []uintptr(nil), New(m).Get("nothing").UintptrSlice()) + assert.Equal(t, val, New(m).Get("nothing").UintptrSlice([]uintptr{uintptr(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustUintptrSlice() + }) + +} + +func TestIsUintptr(t *testing.T) { + + var v *Value + + v = &Value{data: uintptr(1)} + assert.True(t, v.IsUintptr()) + + v = &Value{data: []uintptr{uintptr(1)}} + assert.True(t, v.IsUintptrSlice()) + +} + +func TestEachUintptr(t *testing.T) { + + v := &Value{data: []uintptr{uintptr(1), uintptr(1), uintptr(1), uintptr(1), uintptr(1)}} + count := 0 + replacedVals := make([]uintptr, 0) + assert.Equal(t, v, v.EachUintptr(func(i int, val uintptr) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustUintptrSlice()[0]) + assert.Equal(t, replacedVals[1], v.MustUintptrSlice()[1]) + assert.Equal(t, replacedVals[2], v.MustUintptrSlice()[2]) + +} + +func TestWhereUintptr(t *testing.T) { + + v := &Value{data: []uintptr{uintptr(1), uintptr(1), uintptr(1), uintptr(1), uintptr(1), uintptr(1)}} + + selected := v.WhereUintptr(func(i int, val uintptr) bool { + return i%2 == 0 + }).MustUintptrSlice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupUintptr(t *testing.T) { + + v := &Value{data: []uintptr{uintptr(1), uintptr(1), uintptr(1), uintptr(1), uintptr(1), uintptr(1)}} + + grouped := v.GroupUintptr(func(i int, val uintptr) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]uintptr) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceUintptr(t *testing.T) { + + v := &Value{data: []uintptr{uintptr(1), uintptr(1), uintptr(1), uintptr(1), uintptr(1), uintptr(1)}} + + rawArr := v.MustUintptrSlice() + + replaced := v.ReplaceUintptr(func(index int, val uintptr) uintptr { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustUintptrSlice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectUintptr(t *testing.T) { + + v := &Value{data: []uintptr{uintptr(1), uintptr(1), uintptr(1), uintptr(1), uintptr(1), uintptr(1)}} + + collected := v.CollectUintptr(func(index int, val uintptr) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestFloat32(t *testing.T) { + + val := float32(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Float32()) + assert.Equal(t, val, New(m).Get("value").MustFloat32()) + assert.Equal(t, float32(0), New(m).Get("nothing").Float32()) + assert.Equal(t, val, New(m).Get("nothing").Float32(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustFloat32() + }) + +} + +func TestFloat32Slice(t *testing.T) { + + val := float32(1) + m := map[string]interface{}{"value": []float32{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Float32Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustFloat32Slice()[0]) + assert.Equal(t, []float32(nil), New(m).Get("nothing").Float32Slice()) + assert.Equal(t, val, New(m).Get("nothing").Float32Slice([]float32{float32(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustFloat32Slice() + }) + +} + +func TestIsFloat32(t *testing.T) { + + var v *Value + + v = &Value{data: float32(1)} + assert.True(t, v.IsFloat32()) + + v = &Value{data: []float32{float32(1)}} + assert.True(t, v.IsFloat32Slice()) + +} + +func TestEachFloat32(t *testing.T) { + + v := &Value{data: []float32{float32(1), float32(1), float32(1), float32(1), float32(1)}} + count := 0 + replacedVals := make([]float32, 0) + assert.Equal(t, v, v.EachFloat32(func(i int, val float32) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustFloat32Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustFloat32Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustFloat32Slice()[2]) + +} + +func TestWhereFloat32(t *testing.T) { + + v := &Value{data: []float32{float32(1), float32(1), float32(1), float32(1), float32(1), float32(1)}} + + selected := v.WhereFloat32(func(i int, val float32) bool { + return i%2 == 0 + }).MustFloat32Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupFloat32(t *testing.T) { + + v := &Value{data: []float32{float32(1), float32(1), float32(1), float32(1), float32(1), float32(1)}} + + grouped := v.GroupFloat32(func(i int, val float32) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]float32) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceFloat32(t *testing.T) { + + v := &Value{data: []float32{float32(1), float32(1), float32(1), float32(1), float32(1), float32(1)}} + + rawArr := v.MustFloat32Slice() + + replaced := v.ReplaceFloat32(func(index int, val float32) float32 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustFloat32Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectFloat32(t *testing.T) { + + v := &Value{data: []float32{float32(1), float32(1), float32(1), float32(1), float32(1), float32(1)}} + + collected := v.CollectFloat32(func(index int, val float32) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestFloat64(t *testing.T) { + + val := float64(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Float64()) + assert.Equal(t, val, New(m).Get("value").MustFloat64()) + assert.Equal(t, float64(0), New(m).Get("nothing").Float64()) + assert.Equal(t, val, New(m).Get("nothing").Float64(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustFloat64() + }) + +} + +func TestFloat64Slice(t *testing.T) { + + val := float64(1) + m := map[string]interface{}{"value": []float64{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Float64Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustFloat64Slice()[0]) + assert.Equal(t, []float64(nil), New(m).Get("nothing").Float64Slice()) + assert.Equal(t, val, New(m).Get("nothing").Float64Slice([]float64{float64(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustFloat64Slice() + }) + +} + +func TestIsFloat64(t *testing.T) { + + var v *Value + + v = &Value{data: float64(1)} + assert.True(t, v.IsFloat64()) + + v = &Value{data: []float64{float64(1)}} + assert.True(t, v.IsFloat64Slice()) + +} + +func TestEachFloat64(t *testing.T) { + + v := &Value{data: []float64{float64(1), float64(1), float64(1), float64(1), float64(1)}} + count := 0 + replacedVals := make([]float64, 0) + assert.Equal(t, v, v.EachFloat64(func(i int, val float64) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustFloat64Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustFloat64Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustFloat64Slice()[2]) + +} + +func TestWhereFloat64(t *testing.T) { + + v := &Value{data: []float64{float64(1), float64(1), float64(1), float64(1), float64(1), float64(1)}} + + selected := v.WhereFloat64(func(i int, val float64) bool { + return i%2 == 0 + }).MustFloat64Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupFloat64(t *testing.T) { + + v := &Value{data: []float64{float64(1), float64(1), float64(1), float64(1), float64(1), float64(1)}} + + grouped := v.GroupFloat64(func(i int, val float64) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]float64) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceFloat64(t *testing.T) { + + v := &Value{data: []float64{float64(1), float64(1), float64(1), float64(1), float64(1), float64(1)}} + + rawArr := v.MustFloat64Slice() + + replaced := v.ReplaceFloat64(func(index int, val float64) float64 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustFloat64Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectFloat64(t *testing.T) { + + v := &Value{data: []float64{float64(1), float64(1), float64(1), float64(1), float64(1), float64(1)}} + + collected := v.CollectFloat64(func(index int, val float64) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestComplex64(t *testing.T) { + + val := complex64(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Complex64()) + assert.Equal(t, val, New(m).Get("value").MustComplex64()) + assert.Equal(t, complex64(0), New(m).Get("nothing").Complex64()) + assert.Equal(t, val, New(m).Get("nothing").Complex64(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustComplex64() + }) + +} + +func TestComplex64Slice(t *testing.T) { + + val := complex64(1) + m := map[string]interface{}{"value": []complex64{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Complex64Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustComplex64Slice()[0]) + assert.Equal(t, []complex64(nil), New(m).Get("nothing").Complex64Slice()) + assert.Equal(t, val, New(m).Get("nothing").Complex64Slice([]complex64{complex64(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustComplex64Slice() + }) + +} + +func TestIsComplex64(t *testing.T) { + + var v *Value + + v = &Value{data: complex64(1)} + assert.True(t, v.IsComplex64()) + + v = &Value{data: []complex64{complex64(1)}} + assert.True(t, v.IsComplex64Slice()) + +} + +func TestEachComplex64(t *testing.T) { + + v := &Value{data: []complex64{complex64(1), complex64(1), complex64(1), complex64(1), complex64(1)}} + count := 0 + replacedVals := make([]complex64, 0) + assert.Equal(t, v, v.EachComplex64(func(i int, val complex64) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustComplex64Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustComplex64Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustComplex64Slice()[2]) + +} + +func TestWhereComplex64(t *testing.T) { + + v := &Value{data: []complex64{complex64(1), complex64(1), complex64(1), complex64(1), complex64(1), complex64(1)}} + + selected := v.WhereComplex64(func(i int, val complex64) bool { + return i%2 == 0 + }).MustComplex64Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupComplex64(t *testing.T) { + + v := &Value{data: []complex64{complex64(1), complex64(1), complex64(1), complex64(1), complex64(1), complex64(1)}} + + grouped := v.GroupComplex64(func(i int, val complex64) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]complex64) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceComplex64(t *testing.T) { + + v := &Value{data: []complex64{complex64(1), complex64(1), complex64(1), complex64(1), complex64(1), complex64(1)}} + + rawArr := v.MustComplex64Slice() + + replaced := v.ReplaceComplex64(func(index int, val complex64) complex64 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustComplex64Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectComplex64(t *testing.T) { + + v := &Value{data: []complex64{complex64(1), complex64(1), complex64(1), complex64(1), complex64(1), complex64(1)}} + + collected := v.CollectComplex64(func(index int, val complex64) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} + +// ************************************************************ +// TESTS +// ************************************************************ + +func TestComplex128(t *testing.T) { + + val := complex128(1) + m := map[string]interface{}{"value": val, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Complex128()) + assert.Equal(t, val, New(m).Get("value").MustComplex128()) + assert.Equal(t, complex128(0), New(m).Get("nothing").Complex128()) + assert.Equal(t, val, New(m).Get("nothing").Complex128(1)) + + assert.Panics(t, func() { + New(m).Get("age").MustComplex128() + }) + +} + +func TestComplex128Slice(t *testing.T) { + + val := complex128(1) + m := map[string]interface{}{"value": []complex128{val}, "nothing": nil} + assert.Equal(t, val, New(m).Get("value").Complex128Slice()[0]) + assert.Equal(t, val, New(m).Get("value").MustComplex128Slice()[0]) + assert.Equal(t, []complex128(nil), New(m).Get("nothing").Complex128Slice()) + assert.Equal(t, val, New(m).Get("nothing").Complex128Slice([]complex128{complex128(1)})[0]) + + assert.Panics(t, func() { + New(m).Get("nothing").MustComplex128Slice() + }) + +} + +func TestIsComplex128(t *testing.T) { + + var v *Value + + v = &Value{data: complex128(1)} + assert.True(t, v.IsComplex128()) + + v = &Value{data: []complex128{complex128(1)}} + assert.True(t, v.IsComplex128Slice()) + +} + +func TestEachComplex128(t *testing.T) { + + v := &Value{data: []complex128{complex128(1), complex128(1), complex128(1), complex128(1), complex128(1)}} + count := 0 + replacedVals := make([]complex128, 0) + assert.Equal(t, v, v.EachComplex128(func(i int, val complex128) bool { + + count++ + replacedVals = append(replacedVals, val) + + // abort early + if i == 2 { + return false + } + + return true + + })) + + assert.Equal(t, count, 3) + assert.Equal(t, replacedVals[0], v.MustComplex128Slice()[0]) + assert.Equal(t, replacedVals[1], v.MustComplex128Slice()[1]) + assert.Equal(t, replacedVals[2], v.MustComplex128Slice()[2]) + +} + +func TestWhereComplex128(t *testing.T) { + + v := &Value{data: []complex128{complex128(1), complex128(1), complex128(1), complex128(1), complex128(1), complex128(1)}} + + selected := v.WhereComplex128(func(i int, val complex128) bool { + return i%2 == 0 + }).MustComplex128Slice() + + assert.Equal(t, 3, len(selected)) + +} + +func TestGroupComplex128(t *testing.T) { + + v := &Value{data: []complex128{complex128(1), complex128(1), complex128(1), complex128(1), complex128(1), complex128(1)}} + + grouped := v.GroupComplex128(func(i int, val complex128) string { + return fmt.Sprintf("%v", i%2 == 0) + }).data.(map[string][]complex128) + + assert.Equal(t, 2, len(grouped)) + assert.Equal(t, 3, len(grouped["true"])) + assert.Equal(t, 3, len(grouped["false"])) + +} + +func TestReplaceComplex128(t *testing.T) { + + v := &Value{data: []complex128{complex128(1), complex128(1), complex128(1), complex128(1), complex128(1), complex128(1)}} + + rawArr := v.MustComplex128Slice() + + replaced := v.ReplaceComplex128(func(index int, val complex128) complex128 { + if index < len(rawArr)-1 { + return rawArr[index+1] + } + return rawArr[0] + }) + + replacedArr := replaced.MustComplex128Slice() + if assert.Equal(t, 6, len(replacedArr)) { + assert.Equal(t, replacedArr[0], rawArr[1]) + assert.Equal(t, replacedArr[1], rawArr[2]) + assert.Equal(t, replacedArr[2], rawArr[3]) + assert.Equal(t, replacedArr[3], rawArr[4]) + assert.Equal(t, replacedArr[4], rawArr[5]) + assert.Equal(t, replacedArr[5], rawArr[0]) + } + +} + +func TestCollectComplex128(t *testing.T) { + + v := &Value{data: []complex128{complex128(1), complex128(1), complex128(1), complex128(1), complex128(1), complex128(1)}} + + collected := v.CollectComplex128(func(index int, val complex128) interface{} { + return index + }) + + collectedArr := collected.MustInterSlice() + if assert.Equal(t, 6, len(collectedArr)) { + assert.Equal(t, collectedArr[0], 0) + assert.Equal(t, collectedArr[1], 1) + assert.Equal(t, collectedArr[2], 2) + assert.Equal(t, collectedArr[3], 3) + assert.Equal(t, collectedArr[4], 4) + assert.Equal(t, collectedArr[5], 5) + } + +} diff --git a/third_party/src/github.com/stretchr/objx/value.go b/third_party/src/github.com/stretchr/objx/value.go new file mode 100644 index 00000000000..7aaef06b1ce --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/value.go @@ -0,0 +1,13 @@ +package objx + +// Value provides methods for extracting interface{} data in various +// types. +type Value struct { + // data contains the raw data being managed by this Value + data interface{} +} + +// Data returns the raw data contained by this Value +func (v *Value) Data() interface{} { + return v.data +} diff --git a/third_party/src/github.com/stretchr/objx/value_test.go b/third_party/src/github.com/stretchr/objx/value_test.go new file mode 100644 index 00000000000..0bc65d92c1b --- /dev/null +++ b/third_party/src/github.com/stretchr/objx/value_test.go @@ -0,0 +1 @@ +package objx diff --git a/third_party/src/github.com/stretchr/testify/LICENCE.txt b/third_party/src/github.com/stretchr/testify/LICENCE.txt new file mode 100644 index 00000000000..a009ba467ce --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/LICENCE.txt @@ -0,0 +1,9 @@ +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/third_party/src/github.com/stretchr/testify/README.md b/third_party/src/github.com/stretchr/testify/README.md new file mode 100644 index 00000000000..e8d94cd2575 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/README.md @@ -0,0 +1,230 @@ +Testify - Thou Shalt Write Tests +================================ + +Go code (golang) set of packages that provide many tools for testifying that your code will behave as you intend. + +Features include: + + * [Easy assertions](#assert-package) + * [Mocking](#mock-package) + * [HTTP response trapping](#http-package) + * [Testing suite interfaces and functions](#suite-package) + +Get started: + + * Install testify with [one line of code](#installation), or [update it with another](#staying-up-to-date) + * For an introduction to writing test code in Go, see our [blog post article](http://blog.stretchr.com/2014/03/05/test-driven-development-specifically-in-golang/) or check out http://golang.org/doc/code.html#Testing + * Check out the API Documentation http://godoc.org/github.com/stretchr/testify + * To make your testing life easier, check out our other project, [gorc](http://github.com/stretchr/gorc) + * A little about [Test-Driven Development (TDD)](http://en.wikipedia.org/wiki/Test-driven_development) + + + +`assert` package +---------------- + +The `assert` package provides some helpful methods that allow you to write better test code in Go. Check out the [API documentation for the `assert` package](http://godoc.org/github.com/stretchr/testify/assert). + + * Prints friendly, easy to read failure descriptions + * Allows for very readable code + * Optionally annotate each assertion with a message + +See it in action: + + func TestSomething(t *testing.T) { + + // assert equality + assert.Equal(t, 123, 123, "they should be equal") + + // assert inequality + assert.NotEqual(t, 123, 456, "they should not be equal") + + // assert for nil (good for errors) + assert.Nil(t, object) + + // assert for not nil (good when you expect something) + if assert.NotNil(t, object) { + + // now we know that object isn't nil, we are safe to make + // further assertions without causing any errors + assert.Equal(t, "Something", object.Value) + + } + + } + + * Every assert func takes the `testing.T` object as the first argument. This is how it writes the errors out through the normal `go test` capabilities. + * Every assert func returns a bool indicating whether the assertion was successful or not, this is useful for if you want to go on making further assertions under certain conditions. + +`http` package +-------------- + +The `http` package contains test objects useful for testing code that relies on the `net/http` package. Check out the [API documentation for the `http` package](http://godoc.org/github.com/stretchr/testify/http). + +`mock` package +-------------- + +The `mock` package provides a mechanism for easily writing mock objects that can be used in place of real objects when writing test code. + +An example test function that tests a piece of code that relies on an external object `testObj`, can setup expectations (testify) and assert that they indeed happened: + + package yours + + import ( + "testing" + "github.com/stretchr/testify/mock" + ) + + /* + Test objects + */ + + // MyMockedObject is a mocked object that implements an interface + // that describes an object that the code I am testing relies on. + type MyMockedObject struct{ + mock.Mock + } + + // DoSomething is a method on MyMockedObject that implements some interface + // and just records the activity, and returns what the Mock object tells it to. + // + // In the real object, this method would do something useful, but since this + // is a mocked object - we're just going to stub it out. + // + // NOTE: This method is not being tested here, code that uses this object is. + func (m *MyMockedObject) DoSomething(number int) (bool, error) { + + args := m.Mock.Called(number) + return args.Bool(0), args.Error(1) + + } + + /* + Actual test functions + */ + + // TestSomething is an example of how to use our test object to + // make assertions about some target code we are testing. + func TestSomething(t *testing.T) { + + // create an instance of our test object + testObj := new(MyMockedObject) + + // setup expectations + testObj.On("DoSomething", 123).Return(true, nil) + + // call the code we are testing + targetFuncThatDoesSomethingWithObj(testObj) + + // assert that the expectations were met + testObj.Mock.AssertExpectations(t) + + } + +For more information on how to write mock code, check out the [API documentation for the `mock` package](http://godoc.org/github.com/stretchr/testify/mock). + +`suite` package +--------------- + +The `suite` package provides functionality that you might be used to from more common object oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal. + +An example suite is shown below: + + // Basic imports + import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + ) + + // Define the suite, and absorb the built-in basic suite + // functionality from testify - including a T() method which + // returns the current testing context + type ExampleTestSuite struct { + suite.Suite + VariableThatShouldStartAtFive int + } + + // Make sure that VariableThatShouldStartAtFive is set to five + // before each test + func (suite *ExampleTestSuite) SetupTest() { + suite.VariableThatShouldStartAtFive = 5 + } + + // All methods that begin with "Test" are run as tests within a + // suite. + func (suite *ExampleTestSuite) TestExample() { + assert.Equal(suite.T(), suite.VariableThatShouldStartAtFive, 5) + } + + // In order for 'go test' to run this suite, we need to create + // a normal test function and pass our suite to suite.Run + func TestExampleTestSuite(t *testing.T) { + suite.Run(t, new(ExampleTestSuite)) + } + +For a more complete example, using all of the functionality provided by the suite package, look at our [example testing suite](https://github.com/stretchr/testify/blob/master/suite/suite_test.go) + +For more information on writing suites, check out the [API documentation for the `suite` package](http://godoc.org/github.com/stretchr/testify/suite). + +------ + +Installation +============ + +To install Testify, use `go get`: + + go get github.com/stretchr/testify + +This will then make the following packages available to you: + + github.com/stretchr/testify/assert + github.com/stretchr/testify/mock + github.com/stretchr/testify/http + +Import the `testify/assert` package into your code using this template: + + package yours + + import ( + "testing" + "github.com/stretchr/testify/assert" + ) + + func TestSomething(t *testing.T) { + + assert.True(t, true, "True is true!") + + } + +------ + +Staying up to date +================== + +To update Testify, use `go get -u`: + + go get -u github.com/stretchr/testify + +------ + +Contributing +============ + +Please feel free to submit issues, fork the repository and send pull requests! + +When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. Extra credit for those using Testify to write the test code that demonstrates it. + +------ + +Licence +======= +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third_party/src/github.com/stretchr/testify/assert/assertions.go b/third_party/src/github.com/stretchr/testify/assert/assertions.go new file mode 100644 index 00000000000..72247d00af3 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/assert/assertions.go @@ -0,0 +1,537 @@ +package assert + +import ( + "fmt" + "reflect" + "runtime" + "strings" + "time" +) + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) +} + +// Comparison a custom function that returns true on success and false on failure +type Comparison func() (success bool) + +/* + Helper functions +*/ + +// ObjectsAreEqual determines if two objects are considered equal. +// +// This function does no assertion of any kind. +func ObjectsAreEqual(expected, actual interface{}) bool { + + if expected == nil || actual == nil { + return expected == actual + } + + if reflect.DeepEqual(expected, actual) { + return true + } + + expectedValue := reflect.ValueOf(expected) + actualValue := reflect.ValueOf(actual) + if expectedValue == actualValue { + return true + } + + // Attempt comparison after type conversion + if actualValue.Type().ConvertibleTo(expectedValue.Type()) && expectedValue == actualValue.Convert(expectedValue.Type()) { + return true + } + + // Last ditch effort + if fmt.Sprintf("%#v", expected) == fmt.Sprintf("%#v", actual) { + return true + } + + return false + +} + +/* CallerInfo is necessary because the assert functions use the testing object +internally, causing it to print the file:line of the assert method, rather than where +the problem actually occured in calling code.*/ + +// CallerInfo returns a string containing the file and line number of the assert call +// that failed. +func CallerInfo() string { + + file := "" + line := 0 + ok := false + + for i := 0; ; i++ { + _, file, line, ok = runtime.Caller(i) + if !ok { + return "" + } + parts := strings.Split(file, "/") + dir := parts[len(parts)-2] + file = parts[len(parts)-1] + if (dir != "assert" && dir != "mock") || file == "mock_test.go" { + break + } + } + + return fmt.Sprintf("%s:%d", file, line) +} + +// getWhitespaceString returns a string that is long enough to overwrite the default +// output from the go testing framework. +func getWhitespaceString() string { + + _, file, line, ok := runtime.Caller(1) + if !ok { + return "" + } + parts := strings.Split(file, "/") + file = parts[len(parts)-1] + + return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line))) + +} + +func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { + if len(msgAndArgs) == 0 || msgAndArgs == nil { + return "" + } + if len(msgAndArgs) == 1 { + return msgAndArgs[0].(string) + } + if len(msgAndArgs) > 1 { + return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) + } + return "" +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + + message := messageFromMsgAndArgs(msgAndArgs...) + + if len(message) > 0 { + t.Errorf("\r%s\r\tLocation:\t%s\n\r\tError:\t\t%s\n\r\tMessages:\t%s\n\r", getWhitespaceString(), CallerInfo(), failureMessage, message) + } else { + t.Errorf("\r%s\r\tLocation:\t%s\n\r\tError:\t\t%s\n\r", getWhitespaceString(), CallerInfo(), failureMessage) + } + + return false +} + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { + + interfaceType := reflect.TypeOf(interfaceObject).Elem() + + if !reflect.TypeOf(object).Implements(interfaceType) { + return Fail(t, fmt.Sprintf("Object must implement %v", interfaceType), msgAndArgs...) + } + + return true + +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + + if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) { + return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...) + } + + return true +} + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123, "123 and 123 should be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + + if !ObjectsAreEqual(expected, actual) { + return Fail(t, fmt.Sprintf("Not equal: %#v != %#v", expected, actual), msgAndArgs...) + } + + return true + +} + +// Exactly asserts that two objects are equal is value and type. +// +// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + + aType := reflect.TypeOf(expected) + bType := reflect.TypeOf(actual) + + if aType != bType { + return Fail(t, "Types expected to match exactly", "%v != %v", aType, bType) + } + + return Equal(t, expected, actual, msgAndArgs...) + +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err, "err should be something") +// +// Returns whether the assertion was successful (true) or not (false). +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + + success := true + + if object == nil { + success = false + } else { + value := reflect.ValueOf(object) + kind := value.Kind() + if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { + success = false + } + } + + if !success { + Fail(t, "Expected not to be nil.", msgAndArgs...) + } + + return success +} + +// isNil checks if a specified object is nil or not, without Failing. +func isNil(object interface{}) bool { + if object == nil { + return true + } + + value := reflect.ValueOf(object) + kind := value.Kind() + if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { + return true + } + + return false +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err, "err should be nothing") +// +// Returns whether the assertion was successful (true) or not (false). +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if isNil(object) { + return true + } + return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) +} + +var zeros = []interface{}{ + int(0), + int8(0), + int16(0), + int32(0), + int64(0), + uint(0), + uint8(0), + uint16(0), + uint32(0), + uint64(0), + float32(0), + float64(0), +} + +// isEmpty gets whether the specified object is considered empty or not. +func isEmpty(object interface{}) bool { + + if object == nil { + return true + } else if object == "" { + return true + } else if object == false { + return true + } + + for _, v := range zeros { + if object == v { + return true + } + } + + objValue := reflect.ValueOf(object) + + switch objValue.Kind() { + case reflect.Map: + fallthrough + case reflect.Slice, reflect.Chan: + { + return (objValue.Len() == 0) + } + case reflect.Ptr: + { + switch object.(type) { + case *time.Time: + return object.(*time.Time).IsZero() + default: + return false + } + } + } + return false +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +// +// Returns whether the assertion was successful (true) or not (false). +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + + pass := isEmpty(object) + if !pass { + Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...) + } + + return pass + +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + + pass := !isEmpty(object) + if !pass { + Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...) + } + + return pass + +} + +// True asserts that the specified value is true. +// +// assert.True(t, myBool, "myBool should be true") +// +// Returns whether the assertion was successful (true) or not (false). +func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { + + if value != true { + return Fail(t, "Should be true", msgAndArgs...) + } + + return true + +} + +// False asserts that the specified value is true. +// +// assert.False(t, myBool, "myBool should be false") +// +// Returns whether the assertion was successful (true) or not (false). +func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { + + if value != false { + return Fail(t, "Should be false", msgAndArgs...) + } + + return true + +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") +// +// Returns whether the assertion was successful (true) or not (false). +func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + + if ObjectsAreEqual(expected, actual) { + return Fail(t, "Should not be equal", msgAndArgs...) + } + + return true + +} + +// Contains asserts that the specified string contains the specified substring. +// +// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") +// +// Returns whether the assertion was successful (true) or not (false). +func Contains(t TestingT, s, contains string, msgAndArgs ...interface{}) bool { + + if !strings.Contains(s, contains) { + return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", s, contains), msgAndArgs...) + } + + return true + +} + +// NotContains asserts that the specified string does NOT contain the specified substring. +// +// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") +// +// Returns whether the assertion was successful (true) or not (false). +func NotContains(t TestingT, s, contains string, msgAndArgs ...interface{}) bool { + + if strings.Contains(s, contains) { + return Fail(t, fmt.Sprintf("\"%s\" should not contain \"%s\"", s, contains), msgAndArgs...) + } + + return true + +} + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { + result := comp() + if !result { + Fail(t, "Condition failed!", msgAndArgs...) + } + return result +} + +// PanicTestFunc defines a func that should be passed to the assert.Panics and assert.NotPanics +// methods, and represents a simple func that takes no arguments, and returns nothing. +type PanicTestFunc func() + +// didPanic returns true if the function passed to it panics. Otherwise, it returns false. +func didPanic(f PanicTestFunc) (bool, interface{}) { + + didPanic := false + var message interface{} + func() { + + defer func() { + if message = recover(); message != nil { + didPanic = true + } + }() + + // call the target function + f() + + }() + + return didPanic, message + +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ +// GoCrazy() +// }, "Calling GoCrazy() should panic") +// +// Returns whether the assertion was successful (true) or not (false). +func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + + if funcDidPanic, panicValue := didPanic(f); !funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + } + + return true +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ +// RemainCalm() +// }, "Calling RemainCalm() should NOT panic") +// +// Returns whether the assertion was successful (true) or not (false). +func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + + if funcDidPanic, panicValue := didPanic(f); funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should not panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + } + + return true +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") +// +// Returns whether the assertion was successful (true) or not (false). +func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + + dt := expected.Sub(actual) + if dt < -delta || dt > delta { + return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, dt, delta), msgAndArgs...) + } + + return true +} + +/* + Errors +*/ + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, actualObj, expectedObj) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { + if isNil(err) { + return true + } + + return Fail(t, fmt.Sprintf("No error is expected but got %v", err), msgAndArgs...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { + + message := messageFromMsgAndArgs(msgAndArgs...) + return NotNil(t, err, "An error is expected but got nil. %s", message) + +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err, "An error was expected") { +// assert.Equal(t, err, expectedError) +// } +// +// Returns whether the assertion was successful (true) or not (false). +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { + + message := messageFromMsgAndArgs(msgAndArgs...) + if !NotNil(t, theError, "An error is expected but got nil. %s", message) { + return false + } + s := "An error with value \"%s\" is expected but got \"%s\". %s" + return Equal(t, theError.Error(), errString, + s, errString, theError.Error(), message) +} diff --git a/third_party/src/github.com/stretchr/testify/assert/assertions_test.go b/third_party/src/github.com/stretchr/testify/assert/assertions_test.go new file mode 100644 index 00000000000..d006169676b --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/assert/assertions_test.go @@ -0,0 +1,420 @@ +package assert + +import ( + "errors" + "testing" + "time" +) + +// AssertionTesterInterface defines an interface to be used for testing assertion methods +type AssertionTesterInterface interface { + TestMethod() +} + +// AssertionTesterConformingObject is an object that conforms to the AssertionTesterInterface interface +type AssertionTesterConformingObject struct { +} + +func (a *AssertionTesterConformingObject) TestMethod() { +} + +// AssertionTesterNonConformingObject is an object that does not conform to the AssertionTesterInterface interface +type AssertionTesterNonConformingObject struct { +} + +func TestObjectsAreEqual(t *testing.T) { + + if !ObjectsAreEqual("Hello World", "Hello World") { + t.Error("objectsAreEqual should return true") + } + if !ObjectsAreEqual(123, 123) { + t.Error("objectsAreEqual should return true") + } + if !ObjectsAreEqual(123.5, 123.5) { + t.Error("objectsAreEqual should return true") + } + if !ObjectsAreEqual([]byte("Hello World"), []byte("Hello World")) { + t.Error("objectsAreEqual should return true") + } + if !ObjectsAreEqual(nil, nil) { + t.Error("objectsAreEqual should return true") + } + +} + +func TestImplements(t *testing.T) { + + mockT := new(testing.T) + + if !Implements(mockT, (*AssertionTesterInterface)(nil), new(AssertionTesterConformingObject)) { + t.Error("Implements method should return true: AssertionTesterConformingObject implements AssertionTesterInterface") + } + if Implements(mockT, (*AssertionTesterInterface)(nil), new(AssertionTesterNonConformingObject)) { + t.Error("Implements method should return false: AssertionTesterNonConformingObject does not implements AssertionTesterInterface") + } + +} + +func TestIsType(t *testing.T) { + + mockT := new(testing.T) + + if !IsType(mockT, new(AssertionTesterConformingObject), new(AssertionTesterConformingObject)) { + t.Error("IsType should return true: AssertionTesterConformingObject is the same type as AssertionTesterConformingObject") + } + if IsType(mockT, new(AssertionTesterConformingObject), new(AssertionTesterNonConformingObject)) { + t.Error("IsType should return false: AssertionTesterConformingObject is not the same type as AssertionTesterNonConformingObject") + } + +} + +func TestEqual(t *testing.T) { + + mockT := new(testing.T) + + if !Equal(mockT, "Hello World", "Hello World") { + t.Error("Equal should return true") + } + if !Equal(mockT, 123, 123) { + t.Error("Equal should return true") + } + if !Equal(mockT, 123.5, 123.5) { + t.Error("Equal should return true") + } + if !Equal(mockT, []byte("Hello World"), []byte("Hello World")) { + t.Error("Equal should return true") + } + if !Equal(mockT, nil, nil) { + t.Error("Equal should return true") + } + if !Equal(mockT, int32(123), int64(123)) { + t.Error("Equal should return true") + } + if !Equal(mockT, int64(123), uint64(123)) { + t.Error("Equal should return true") + } + +} + +func TestNotNil(t *testing.T) { + + mockT := new(testing.T) + + if !NotNil(mockT, new(AssertionTesterConformingObject)) { + t.Error("NotNil should return true: object is not nil") + } + if NotNil(mockT, nil) { + t.Error("NotNil should return false: object is nil") + } + +} + +func TestNil(t *testing.T) { + + mockT := new(testing.T) + + if !Nil(mockT, nil) { + t.Error("Nil should return true: object is nil") + } + if Nil(mockT, new(AssertionTesterConformingObject)) { + t.Error("Nil should return false: object is not nil") + } + +} + +func TestTrue(t *testing.T) { + + mockT := new(testing.T) + + if !True(mockT, true) { + t.Error("True should return true") + } + if True(mockT, false) { + t.Error("True should return false") + } + +} + +func TestFalse(t *testing.T) { + + mockT := new(testing.T) + + if !False(mockT, false) { + t.Error("False should return true") + } + if False(mockT, true) { + t.Error("False should return false") + } + +} + +func TestExactly(t *testing.T) { + + mockT := new(testing.T) + + a := float32(1) + b := float64(1) + c := float32(1) + d := float32(2) + + if Exactly(mockT, a, b) { + t.Error("Exactly should return false") + } + if Exactly(mockT, a, d) { + t.Error("Exactly should return false") + } + if !Exactly(mockT, a, c) { + t.Error("Exactly should return true") + } + + if Exactly(mockT, nil, a) { + t.Error("Exactly should return false") + } + if Exactly(mockT, a, nil) { + t.Error("Exactly should return false") + } + +} + +func TestNotEqual(t *testing.T) { + + mockT := new(testing.T) + + if !NotEqual(mockT, "Hello World", "Hello World!") { + t.Error("NotEqual should return true") + } + if !NotEqual(mockT, 123, 1234) { + t.Error("NotEqual should return true") + } + if !NotEqual(mockT, 123.5, 123.55) { + t.Error("NotEqual should return true") + } + if !NotEqual(mockT, []byte("Hello World"), []byte("Hello World!")) { + t.Error("NotEqual should return true") + } + if !NotEqual(mockT, nil, new(AssertionTesterConformingObject)) { + t.Error("NotEqual should return true") + } +} + +func TestContains(t *testing.T) { + + mockT := new(testing.T) + + if !Contains(mockT, "Hello World", "Hello") { + t.Error("Contains should return true: \"Hello World\" contains \"Hello\"") + } + if Contains(mockT, "Hello World", "Salut") { + t.Error("Contains should return false: \"Hello World\" does not contain \"Salut\"") + } + +} + +func TestNotContains(t *testing.T) { + + mockT := new(testing.T) + + if !NotContains(mockT, "Hello World", "Hello!") { + t.Error("NotContains should return true: \"Hello World\" does not contain \"Hello!\"") + } + if NotContains(mockT, "Hello World", "Hello") { + t.Error("NotContains should return false: \"Hello World\" contains \"Hello\"") + } + +} + +func TestDidPanic(t *testing.T) { + + if funcDidPanic, _ := didPanic(func() { + panic("Panic!") + }); !funcDidPanic { + t.Error("didPanic should return true") + } + + if funcDidPanic, _ := didPanic(func() { + }); funcDidPanic { + t.Error("didPanic should return false") + } + +} + +func TestPanics(t *testing.T) { + + mockT := new(testing.T) + + if !Panics(mockT, func() { + panic("Panic!") + }) { + t.Error("Panics should return true") + } + + if Panics(mockT, func() { + }) { + t.Error("Panics should return false") + } + +} + +func TestNotPanics(t *testing.T) { + + mockT := new(testing.T) + + if !NotPanics(mockT, func() { + }) { + t.Error("NotPanics should return true") + } + + if NotPanics(mockT, func() { + panic("Panic!") + }) { + t.Error("NotPanics should return false") + } + +} + +func TestEqual_Funcs(t *testing.T) { + + type f func() int + f1 := func() int { return 1 } + f2 := func() int { return 2 } + + f1Copy := f1 + + Equal(t, f1Copy, f1, "Funcs are the same and should be considered equal") + NotEqual(t, f1, f2, "f1 and f2 are different") + +} + +func TestNoError(t *testing.T) { + + mockT := new(testing.T) + + // start with a nil error + var err error + + True(t, NoError(mockT, err), "NoError should return True for nil arg") + + // now set an error + err = errors.New("some error") + + False(t, NoError(mockT, err), "NoError with error should return False") + +} + +func TestError(t *testing.T) { + + mockT := new(testing.T) + + // start with a nil error + var err error + + False(t, Error(mockT, err), "Error should return False for nil arg") + + // now set an error + err = errors.New("some error") + + True(t, Error(mockT, err), "Error with error should return True") + +} + +func TestEqualError(t *testing.T) { + mockT := new(testing.T) + + // start with a nil error + var err error + False(t, EqualError(mockT, err, ""), + "EqualError should return false for nil arg") + + // now set an error + err = errors.New("some error") + False(t, EqualError(mockT, err, "Not some error"), + "EqualError should return false for different error string") + True(t, EqualError(mockT, err, "some error"), + "EqualError should return true") +} + +func Test_isEmpty(t *testing.T) { + + chWithValue := make(chan struct{}, 1) + chWithValue <- struct{}{} + + True(t, isEmpty("")) + True(t, isEmpty(nil)) + True(t, isEmpty([]string{})) + True(t, isEmpty(0)) + True(t, isEmpty(int32(0))) + True(t, isEmpty(int64(0))) + True(t, isEmpty(false)) + True(t, isEmpty(map[string]string{})) + True(t, isEmpty(new(time.Time))) + True(t, isEmpty(make(chan struct{}))) + False(t, isEmpty("something")) + False(t, isEmpty(errors.New("something"))) + False(t, isEmpty([]string{"something"})) + False(t, isEmpty(1)) + False(t, isEmpty(true)) + False(t, isEmpty(map[string]string{"Hello": "World"})) + False(t, isEmpty(chWithValue)) + +} + +func TestEmpty(t *testing.T) { + + mockT := new(testing.T) + chWithValue := make(chan struct{}, 1) + chWithValue <- struct{}{} + + True(t, Empty(mockT, ""), "Empty string is empty") + True(t, Empty(mockT, nil), "Nil is empty") + True(t, Empty(mockT, []string{}), "Empty string array is empty") + True(t, Empty(mockT, 0), "Zero int value is empty") + True(t, Empty(mockT, false), "False value is empty") + True(t, Empty(mockT, make(chan struct{})), "Channel without values is empty") + + False(t, Empty(mockT, "something"), "Non Empty string is not empty") + False(t, Empty(mockT, errors.New("something")), "Non nil object is not empty") + False(t, Empty(mockT, []string{"something"}), "Non empty string array is not empty") + False(t, Empty(mockT, 1), "Non-zero int value is not empty") + False(t, Empty(mockT, true), "True value is not empty") + False(t, Empty(mockT, chWithValue), "Channel with values is not empty") +} + +func TestNotEmpty(t *testing.T) { + + mockT := new(testing.T) + chWithValue := make(chan struct{}, 1) + chWithValue <- struct{}{} + + False(t, NotEmpty(mockT, ""), "Empty string is empty") + False(t, NotEmpty(mockT, nil), "Nil is empty") + False(t, NotEmpty(mockT, []string{}), "Empty string array is empty") + False(t, NotEmpty(mockT, 0), "Zero int value is empty") + False(t, NotEmpty(mockT, false), "False value is empty") + False(t, NotEmpty(mockT, make(chan struct{})), "Channel without values is empty") + + True(t, NotEmpty(mockT, "something"), "Non Empty string is not empty") + True(t, NotEmpty(mockT, errors.New("something")), "Non nil object is not empty") + True(t, NotEmpty(mockT, []string{"something"}), "Non empty string array is not empty") + True(t, NotEmpty(mockT, 1), "Non-zero int value is not empty") + True(t, NotEmpty(mockT, true), "True value is not empty") + True(t, NotEmpty(mockT, chWithValue), "Channel with values is not empty") +} + +func TestWithinDuration(t *testing.T) { + + mockT := new(testing.T) + a := time.Now() + b := a.Add(10 * time.Second) + + True(t, WithinDuration(mockT, a, b, 10*time.Second), "A 10s difference is within a 10s time difference") + True(t, WithinDuration(mockT, b, a, 10*time.Second), "A 10s difference is within a 10s time difference") + + False(t, WithinDuration(mockT, a, b, 9*time.Second), "A 10s difference is not within a 9s time difference") + False(t, WithinDuration(mockT, b, a, 9*time.Second), "A 10s difference is not within a 9s time difference") + + False(t, WithinDuration(mockT, a, b, -9*time.Second), "A 10s difference is not within a 9s time difference") + False(t, WithinDuration(mockT, b, a, -9*time.Second), "A 10s difference is not within a 9s time difference") + + False(t, WithinDuration(mockT, a, b, -11*time.Second), "A 10s difference is not within a 9s time difference") + False(t, WithinDuration(mockT, b, a, -11*time.Second), "A 10s difference is not within a 9s time difference") +} diff --git a/third_party/src/github.com/stretchr/testify/assert/doc.go b/third_party/src/github.com/stretchr/testify/assert/doc.go new file mode 100644 index 00000000000..25f699bd6ed --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/assert/doc.go @@ -0,0 +1,74 @@ +// A set of comprehensive testing tools for use with the normal Go testing system. +// +// Example Usage +// +// The following is a complete example using assert in a standard test function: +// import ( +// "testing" +// "github.com/stretchr/testify/assert" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// assert.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// Assertions +// +// Assertions allow you to easily write test code, and are global funcs in the `assert` package. +// All assertion functions take, as the first argument, the `*testing.T` object provided by the +// testing framework. This allows the assertion funcs to write the failings and other details to +// the correct place. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +// +// Here is an overview of the assert functions: +// +// assert.Equal(t, expected, actual [, message [, format-args]) +// +// assert.NotEqual(t, notExpected, actual [, message [, format-args]]) +// +// assert.True(t, actualBool [, message [, format-args]]) +// +// assert.False(t, actualBool [, message [, format-args]]) +// +// assert.Nil(t, actualObject [, message [, format-args]]) +// +// assert.NotNil(t, actualObject [, message [, format-args]]) +// +// assert.Empty(t, actualObject [, message [, format-args]]) +// +// assert.NotEmpty(t, actualObject [, message [, format-args]]) +// +// assert.Error(t, errorObject [, message [, format-args]]) +// +// assert.NoError(t, errorObject [, message [, format-args]]) +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject) [,message [, format-args]]) +// +// assert.IsType(t, expectedObject, actualObject [, message [, format-args]]) +// +// assert.Contains(t, string, substring [, message [, format-args]]) +// +// assert.NotContains(t, string, substring [, message [, format-args]]) +// +// assert.Panics(t, func(){ +// +// // call code that should panic +// +// } [, message [, format-args]]) +// +// assert.NotPanics(t, func(){ +// +// // call code that should not panic +// +// } [, message [, format-args]]) +// +// assert.WithinDuration(t, timeA, timeB, deltaTime, [, message [, format-args]]) + +package assert diff --git a/third_party/src/github.com/stretchr/testify/assert/errors.go b/third_party/src/github.com/stretchr/testify/assert/errors.go new file mode 100644 index 00000000000..ac9dc9d1d61 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/assert/errors.go @@ -0,0 +1,10 @@ +package assert + +import ( + "errors" +) + +// AnError is an error instance useful for testing. If the code does not care +// about error specifics, and only needs to return the error for example, this +// error should be used to make the test code more readable. +var AnError = errors.New("assert.AnError general error for testing") diff --git a/third_party/src/github.com/stretchr/testify/doc.go b/third_party/src/github.com/stretchr/testify/doc.go new file mode 100644 index 00000000000..3bde65c1900 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/doc.go @@ -0,0 +1,18 @@ +// A set of packages that provide many tools for testifying that your code will behave as you intend. +// +// testify contains the following packages: +// +// The assert package provides a comprehensive set of assertion functions that tie in to the Go testing system. +// +// The http package contains tools to make it easier to test http activity using the Go testing system. +// +// The mock package provides a system by which it is possible to mock your objects and verify calls are happening as expected. +// +// The suite package provides a basic structure for using structs as testing suites, and methods on those structs as tests. It includes setup/teardown functionality in the way of interfaces. +package testify + +import ( + _ "github.com/stretchr/testify/assert" + _ "github.com/stretchr/testify/http" + _ "github.com/stretchr/testify/mock" +) diff --git a/third_party/src/github.com/stretchr/testify/http/doc.go b/third_party/src/github.com/stretchr/testify/http/doc.go new file mode 100644 index 00000000000..c7840145111 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/http/doc.go @@ -0,0 +1,2 @@ +// A set of tools to make testing http activity using the Go testing system easier. +package http diff --git a/third_party/src/github.com/stretchr/testify/http/test_response_writer.go b/third_party/src/github.com/stretchr/testify/http/test_response_writer.go new file mode 100644 index 00000000000..efd4d66493f --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/http/test_response_writer.go @@ -0,0 +1,52 @@ +package http + +import ( + "net/http" +) + +// TestResponseWriter is a http.ResponseWriter object that keeps track of all activity +// allowing you to make assertions about how it was used. +// +// DEPRECATED: We recommend you use http://golang.org/pkg/net/http/httptest instead. +type TestResponseWriter struct { + + // StatusCode is the last int written by the call to WriteHeader(int) + StatusCode int + + // Output is a string containing the written bytes using the Write([]byte) func. + Output string + + // header is the internal storage of the http.Header object + header http.Header +} + +// Header gets the http.Header describing the headers that were set in this response. +func (rw *TestResponseWriter) Header() http.Header { + + if rw.header == nil { + rw.header = make(http.Header) + } + + return rw.header +} + +// Write writes the specified bytes to Output. +func (rw *TestResponseWriter) Write(bytes []byte) (int, error) { + + // assume 200 success if no header has been set + if rw.StatusCode == 0 { + rw.WriteHeader(200) + } + + // add these bytes to the output string + rw.Output = rw.Output + string(bytes) + + // return normal values + return 0, nil + +} + +// WriteHeader stores the HTTP status code in the StatusCode. +func (rw *TestResponseWriter) WriteHeader(i int) { + rw.StatusCode = i +} diff --git a/third_party/src/github.com/stretchr/testify/http/test_round_tripper.go b/third_party/src/github.com/stretchr/testify/http/test_round_tripper.go new file mode 100644 index 00000000000..b0ad54527c1 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/http/test_round_tripper.go @@ -0,0 +1,15 @@ +package http + +import ( + "github.com/stretchr/testify/mock" + "net/http" +) + +type TestRoundTripper struct { + mock.Mock +} + +func (t *TestRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + args := t.Called(req) + return args.Get(0).(*http.Response), args.Error(1) +} diff --git a/third_party/src/github.com/stretchr/testify/mock/doc.go b/third_party/src/github.com/stretchr/testify/mock/doc.go new file mode 100644 index 00000000000..7d4e7b8d391 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/mock/doc.go @@ -0,0 +1,43 @@ +// Provides a system by which it is possible to mock your objects and verify calls are happening as expected. +// +// Example Usage +// +// The mock package provides an object, Mock, that tracks activity on another object. It is usually +// embedded into a test object as shown below: +// +// type MyTestObject struct { +// // add a Mock object instance +// mock.Mock +// +// // other fields go here as normal +// } +// +// When implementing the methods of an interface, you wire your functions up +// to call the Mock.Called(args...) method, and return the appropriate values. +// +// For example, to mock a method that saves the name and age of a person and returns +// the year of their birth or an error, you might write this: +// +// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) { +// args := o.Mock.Called(firstname, lastname, age) +// return args.Int(0), args.Error(1) +// } +// +// The Int, Error and Bool methods are examples of strongly typed getters that take the argument +// index position. Given this argument list: +// +// (12, true, "Something") +// +// You could read them out strongly typed like this: +// +// args.Int(0) +// args.Bool(1) +// args.String(2) +// +// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion: +// +// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine) +// +// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those +// cases you should check for nil first. +package mock diff --git a/third_party/src/github.com/stretchr/testify/mock/mock.go b/third_party/src/github.com/stretchr/testify/mock/mock.go new file mode 100644 index 00000000000..eb003672221 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/mock/mock.go @@ -0,0 +1,505 @@ +package mock + +import ( + "fmt" + "github.com/stretchr/objx" + "github.com/stretchr/testify/assert" + "reflect" + "runtime" + "strings" +) + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Logf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) +} + +/* + Call +*/ + +// Call represents a method call and is used for setting expectations, +// as well as recording activity. +type Call struct { + + // The name of the method that was or will be called. + Method string + + // Holds the arguments of the method. + Arguments Arguments + + // Holds the arguments that should be returned when + // this method is called. + ReturnArguments Arguments + + // The number of times to return the return arguments when setting + // expectations. 0 means to always return the value. + Repeatability int +} + +// Mock is the workhorse used to track activity on another object. +// For an example of its usage, refer to the "Example Usage" section at the top of this document. +type Mock struct { + + // The method name that is currently + // being referred to by the On method. + onMethodName string + + // An array of the arguments that are + // currently being referred to by the On method. + onMethodArguments Arguments + + // Represents the calls that are expected of + // an object. + ExpectedCalls []Call + + // Holds the calls that were made to this mocked object. + Calls []Call + + // TestData holds any data that might be useful for testing. Testify ignores + // this data completely allowing you to do whatever you like with it. + testData objx.Map +} + +// TestData holds any data that might be useful for testing. Testify ignores +// this data completely allowing you to do whatever you like with it. +func (m *Mock) TestData() objx.Map { + + if m.testData == nil { + m.testData = make(objx.Map) + } + + return m.testData +} + +/* + Setting expectations +*/ + +// On starts a description of an expectation of the specified method +// being called. +// +// Mock.On("MyMethod", arg1, arg2) +func (m *Mock) On(methodName string, arguments ...interface{}) *Mock { + m.onMethodName = methodName + m.onMethodArguments = arguments + return m +} + +// Return finishes a description of an expectation of the method (and arguments) +// specified in the most recent On method call. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2) +func (m *Mock) Return(returnArguments ...interface{}) *Mock { + m.ExpectedCalls = append(m.ExpectedCalls, Call{m.onMethodName, m.onMethodArguments, returnArguments, 0}) + return m +} + +// Once indicates that that the mock should only return the value once. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() +func (m *Mock) Once() { + m.ExpectedCalls[len(m.ExpectedCalls)-1].Repeatability = 1 +} + +// Twice indicates that that the mock should only return the value twice. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() +func (m *Mock) Twice() { + m.ExpectedCalls[len(m.ExpectedCalls)-1].Repeatability = 2 +} + +// Times indicates that that the mock should only return the indicated number +// of times. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) +func (m *Mock) Times(i int) { + m.ExpectedCalls[len(m.ExpectedCalls)-1].Repeatability = i +} + +/* + Recording and responding to activity +*/ + +func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) { + for i, call := range m.ExpectedCalls { + if call.Method == method && call.Repeatability > -1 { + + _, diffCount := call.Arguments.Diff(arguments) + if diffCount == 0 { + return i, &call + } + + } + } + return -1, nil +} + +func (m *Mock) findClosestCall(method string, arguments ...interface{}) (bool, *Call) { + + diffCount := 0 + var closestCall *Call = nil + + for _, call := range m.ExpectedCalls { + if call.Method == method { + + _, tempDiffCount := call.Arguments.Diff(arguments) + if tempDiffCount < diffCount || diffCount == 0 { + diffCount = tempDiffCount + closestCall = &call + } + + } + } + + if closestCall == nil { + return false, nil + } + + return true, closestCall +} + +func callString(method string, arguments Arguments, includeArgumentValues bool) string { + + var argValsString string = "" + if includeArgumentValues { + var argVals []string + for argIndex, arg := range arguments { + argVals = append(argVals, fmt.Sprintf("%d: %v", argIndex, arg)) + } + argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t")) + } + + return fmt.Sprintf("%s(%s)%s", method, arguments.String(), argValsString) +} + +// Called tells the mock object that a method has been called, and gets an array +// of arguments to return. Panics if the call is unexpected (i.e. not preceeded by +// appropriate .On .Return() calls) +func (m *Mock) Called(arguments ...interface{}) Arguments { + + // get the calling function's name + pc, _, _, ok := runtime.Caller(1) + if !ok { + panic("Couldn't get the caller information") + } + functionPath := runtime.FuncForPC(pc).Name() + parts := strings.Split(functionPath, ".") + functionName := parts[len(parts)-1] + + found, call := m.findExpectedCall(functionName, arguments...) + + switch { + case found < 0: + // we have to fail here - because we don't know what to do + // as the return arguments. This is because: + // + // a) this is a totally unexpected call to this method, + // b) the arguments are not what was expected, or + // c) the developer has forgotten to add an accompanying On...Return pair. + + closestFound, closestCall := m.findClosestCall(functionName, arguments...) + + if closestFound { + panic(fmt.Sprintf("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n", callString(functionName, arguments, true), callString(functionName, closestCall.Arguments, true))) + } else { + panic(fmt.Sprintf("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", functionName, functionName, callString(functionName, arguments, true), assert.CallerInfo())) + } + case call.Repeatability == 1: + call.Repeatability = -1 + m.ExpectedCalls[found] = *call + case call.Repeatability > 1: + call.Repeatability -= 1 + m.ExpectedCalls[found] = *call + } + + // add the call + m.Calls = append(m.Calls, Call{functionName, arguments, make([]interface{}, 0), 0}) + + return call.ReturnArguments + +} + +/* + Assertions +*/ + +// AssertExpectationsForObjects asserts that everything specified with On and Return +// of the specified objects was in fact called as expected. +// +// Calls may have occurred in any order. +func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { + var success bool = true + for _, obj := range testObjects { + mockObj := obj.(Mock) + success = success && mockObj.AssertExpectations(t) + } + return success +} + +// AssertExpectations asserts that everything specified with On and Return was +// in fact called as expected. Calls may have occurred in any order. +func (m *Mock) AssertExpectations(t TestingT) bool { + + var somethingMissing bool = false + var failedExpectations int = 0 + + // iterate through each expectation + for _, expectedCall := range m.ExpectedCalls { + switch { + case !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments): + somethingMissing = true + failedExpectations++ + t.Logf("\u274C\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) + case expectedCall.Repeatability > 0: + somethingMissing = true + failedExpectations++ + default: + t.Logf("\u2705\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) + } + } + + if somethingMissing { + t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(m.ExpectedCalls)-failedExpectations, len(m.ExpectedCalls), failedExpectations, assert.CallerInfo()) + } + + return !somethingMissing +} + +// AssertNumberOfCalls asserts that the method was called expectedCalls times. +func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool { + var actualCalls int = 0 + for _, call := range m.Calls { + if call.Method == methodName { + actualCalls++ + } + } + return assert.Equal(t, actualCalls, expectedCalls, fmt.Sprintf("Expected number of calls (%d) does not match the actual number of calls (%d).", expectedCalls, actualCalls)) +} + +// AssertCalled asserts that the method was called. +func (m *Mock) AssertCalled(t TestingT, methodName string, arguments ...interface{}) bool { + if !assert.True(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method should have been called with %d argument(s), but was not.", methodName, len(arguments))) { + t.Logf("%s", m.ExpectedCalls) + return false + } + return true +} + +// AssertNotCalled asserts that the method was not called. +func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...interface{}) bool { + if !assert.False(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method was called with %d argument(s), but should NOT have been.", methodName, len(arguments))) { + t.Logf("%s", m.ExpectedCalls) + return false + } + return true +} + +func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool { + for _, call := range m.Calls { + if call.Method == methodName { + + _, differences := Arguments(expected).Diff(call.Arguments) + + if differences == 0 { + // found the expected call + return true + } + + } + } + // we didn't find the expected call + return false +} + +/* + Arguments +*/ + +// Arguments holds an array of method arguments or return values. +type Arguments []interface{} + +const ( + // The "any" argument. Used in Diff and Assert when + // the argument being tested shouldn't be taken into consideration. + Anything string = "mock.Anything" +) + +// AnythingOfTypeArgument is a string that contains the type of an argument +// for use when type checking. Used in Diff and Assert. +type AnythingOfTypeArgument string + +// AnythingOfType returns an AnythingOfTypeArgument object containing the +// name of the type to check for. Used in Diff and Assert. +// +// For example: +// Assert(t, AnythingOfType("string"), AnythingOfType("int")) +func AnythingOfType(t string) AnythingOfTypeArgument { + return AnythingOfTypeArgument(t) +} + +// Get Returns the argument at the specified index. +func (args Arguments) Get(index int) interface{} { + if index+1 > len(args) { + panic(fmt.Sprintf("assert: arguments: Cannot call Get(%d) because there are %d argument(s).", index, len(args))) + } + return args[index] +} + +// Is gets whether the objects match the arguments specified. +func (args Arguments) Is(objects ...interface{}) bool { + for i, obj := range args { + if obj != objects[i] { + return false + } + } + return true +} + +// Diff gets a string describing the differences between the arguments +// and the specified objects. +// +// Returns the diff string and number of differences found. +func (args Arguments) Diff(objects []interface{}) (string, int) { + + var output string = "\n" + var differences int + + var maxArgCount int = len(args) + if len(objects) > maxArgCount { + maxArgCount = len(objects) + } + + for i := 0; i < maxArgCount; i++ { + var actual, expected interface{} + + if len(objects) <= i { + actual = "(Missing)" + } else { + actual = objects[i] + } + + if len(args) <= i { + expected = "(Missing)" + } else { + expected = args[i] + } + + if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() { + + // type checking + if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) { + // not match + differences++ + output = fmt.Sprintf("%s\t%d: \u274C type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actual) + } + + } else { + + // normal checking + + if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) { + // match + output = fmt.Sprintf("%s\t%d: \u2705 %s == %s\n", output, i, actual, expected) + } else { + // not match + differences++ + output = fmt.Sprintf("%s\t%d: \u274C %s != %s\n", output, i, actual, expected) + } + } + + } + + if differences == 0 { + return "No differences.", differences + } + + return output, differences + +} + +// Assert compares the arguments with the specified objects and fails if +// they do not exactly match. +func (args Arguments) Assert(t TestingT, objects ...interface{}) bool { + + // get the differences + diff, diffCount := args.Diff(objects) + + if diffCount == 0 { + return true + } + + // there are differences... report them... + t.Logf(diff) + t.Errorf("%sArguments do not match.", assert.CallerInfo()) + + return false + +} + +// String gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +// +// If no index is provided, String() returns a complete string representation +// of the arguments. +func (args Arguments) String(indexOrNil ...int) string { + + if len(indexOrNil) == 0 { + // normal String() method - return a string representation of the args + var argsStr []string + for _, arg := range args { + argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg))) + } + return strings.Join(argsStr, ",") + } else if len(indexOrNil) == 1 { + // Index has been specified - get the argument at that index + var index int = indexOrNil[0] + var s string + var ok bool + if s, ok = args.Get(index).(string); !ok { + panic(fmt.Sprintf("assert: arguments: String(%d) failed because object wasn't correct type: %s", index, args.Get(index))) + } + return s + } + + panic(fmt.Sprintf("assert: arguments: Wrong number of arguments passed to String. Must be 0 or 1, not %d", len(indexOrNil))) + +} + +// Int gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Int(index int) int { + var s int + var ok bool + if s, ok = args.Get(index).(int); !ok { + panic(fmt.Sprintf("assert: arguments: Int(%d) failed because object wasn't correct type: %s", index, args.Get(index))) + } + return s +} + +// Error gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Error(index int) error { + obj := args.Get(index) + var s error + var ok bool + if obj == nil { + return nil + } + if s, ok = obj.(error); !ok { + panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %s", index, args.Get(index))) + } + return s +} + +// Bool gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Bool(index int) bool { + var s bool + var ok bool + if s, ok = args.Get(index).(bool); !ok { + panic(fmt.Sprintf("assert: arguments: Bool(%d) failed because object wasn't correct type: %s", index, args.Get(index))) + } + return s +} diff --git a/third_party/src/github.com/stretchr/testify/mock/mock_test.go b/third_party/src/github.com/stretchr/testify/mock/mock_test.go new file mode 100644 index 00000000000..353d8104834 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/mock/mock_test.go @@ -0,0 +1,669 @@ +package mock + +import ( + "errors" + "github.com/stretchr/testify/assert" + "testing" +) + +/* + Test objects +*/ + +// ExampleInterface represents an example interface. +type ExampleInterface interface { + TheExampleMethod(a, b, c int) (int, error) +} + +// TestExampleImplementation is a test implementation of ExampleInterface +type TestExampleImplementation struct { + Mock +} + +func (i *TestExampleImplementation) TheExampleMethod(a, b, c int) (int, error) { + args := i.Mock.Called(a, b, c) + return args.Int(0), errors.New("Whoops") +} + +func (i *TestExampleImplementation) TheExampleMethod2(yesorno bool) { + i.Mock.Called(yesorno) +} + +type ExampleType struct{} + +func (i *TestExampleImplementation) TheExampleMethod3(et *ExampleType) error { + args := i.Mock.Called(et) + return args.Error(0) +} + +/* + Mock +*/ + +func Test_Mock_TestData(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + if assert.NotNil(t, mockedService.TestData()) { + + mockedService.TestData().Set("something", 123) + assert.Equal(t, 123, mockedService.TestData().Get("something").Data()) + + } + +} + +func Test_Mock_On(t *testing.T) { + + // make a test impl object + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + assert.Equal(t, mockedService.Mock.On("TheExampleMethod"), &mockedService.Mock) + assert.Equal(t, "TheExampleMethod", mockedService.Mock.onMethodName) + +} + +func Test_Mock_On_WithArgs(t *testing.T) { + + // make a test impl object + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + assert.Equal(t, mockedService.Mock.On("TheExampleMethod", 1, 2, 3), &mockedService.Mock) + assert.Equal(t, "TheExampleMethod", mockedService.Mock.onMethodName) + assert.Equal(t, 1, mockedService.Mock.onMethodArguments[0]) + assert.Equal(t, 2, mockedService.Mock.onMethodArguments[1]) + assert.Equal(t, 3, mockedService.Mock.onMethodArguments[2]) + +} + +func Test_Mock_Return(t *testing.T) { + + // make a test impl object + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + assert.Equal(t, mockedService.Mock.On("TheExampleMethod", "A", "B", true).Return(1, "two", true), &mockedService.Mock) + + // ensure the call was created + if assert.Equal(t, 1, len(mockedService.Mock.ExpectedCalls)) { + call := mockedService.Mock.ExpectedCalls[0] + + assert.Equal(t, "TheExampleMethod", call.Method) + assert.Equal(t, "A", call.Arguments[0]) + assert.Equal(t, "B", call.Arguments[1]) + assert.Equal(t, true, call.Arguments[2]) + assert.Equal(t, 1, call.ReturnArguments[0]) + assert.Equal(t, "two", call.ReturnArguments[1]) + assert.Equal(t, true, call.ReturnArguments[2]) + assert.Equal(t, 0, call.Repeatability) + + } + +} + +func Test_Mock_Return_Once(t *testing.T) { + + // make a test impl object + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("TheExampleMethod", "A", "B", true).Return(1, "two", true).Once() + + // ensure the call was created + if assert.Equal(t, 1, len(mockedService.Mock.ExpectedCalls)) { + call := mockedService.Mock.ExpectedCalls[0] + + assert.Equal(t, "TheExampleMethod", call.Method) + assert.Equal(t, "A", call.Arguments[0]) + assert.Equal(t, "B", call.Arguments[1]) + assert.Equal(t, true, call.Arguments[2]) + assert.Equal(t, 1, call.ReturnArguments[0]) + assert.Equal(t, "two", call.ReturnArguments[1]) + assert.Equal(t, true, call.ReturnArguments[2]) + assert.Equal(t, 1, call.Repeatability) + + } + +} + +func Test_Mock_Return_Twice(t *testing.T) { + + // make a test impl object + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("TheExampleMethod", "A", "B", true).Return(1, "two", true).Twice() + + // ensure the call was created + if assert.Equal(t, 1, len(mockedService.Mock.ExpectedCalls)) { + call := mockedService.Mock.ExpectedCalls[0] + + assert.Equal(t, "TheExampleMethod", call.Method) + assert.Equal(t, "A", call.Arguments[0]) + assert.Equal(t, "B", call.Arguments[1]) + assert.Equal(t, true, call.Arguments[2]) + assert.Equal(t, 1, call.ReturnArguments[0]) + assert.Equal(t, "two", call.ReturnArguments[1]) + assert.Equal(t, true, call.ReturnArguments[2]) + assert.Equal(t, 2, call.Repeatability) + + } + +} + +func Test_Mock_Return_Times(t *testing.T) { + + // make a test impl object + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("TheExampleMethod", "A", "B", true).Return(1, "two", true).Times(5) + + // ensure the call was created + if assert.Equal(t, 1, len(mockedService.Mock.ExpectedCalls)) { + call := mockedService.Mock.ExpectedCalls[0] + + assert.Equal(t, "TheExampleMethod", call.Method) + assert.Equal(t, "A", call.Arguments[0]) + assert.Equal(t, "B", call.Arguments[1]) + assert.Equal(t, true, call.Arguments[2]) + assert.Equal(t, 1, call.ReturnArguments[0]) + assert.Equal(t, "two", call.ReturnArguments[1]) + assert.Equal(t, true, call.ReturnArguments[2]) + assert.Equal(t, 5, call.Repeatability) + + } + +} + +func Test_Mock_Return_Nothing(t *testing.T) { + + // make a test impl object + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + assert.Equal(t, mockedService.Mock.On("TheExampleMethod", "A", "B", true).Return(), &mockedService.Mock) + + // ensure the call was created + if assert.Equal(t, 1, len(mockedService.Mock.ExpectedCalls)) { + call := mockedService.Mock.ExpectedCalls[0] + + assert.Equal(t, "TheExampleMethod", call.Method) + assert.Equal(t, "A", call.Arguments[0]) + assert.Equal(t, "B", call.Arguments[1]) + assert.Equal(t, true, call.Arguments[2]) + assert.Equal(t, 0, len(call.ReturnArguments)) + + } + +} + +func Test_Mock_findExpectedCall(t *testing.T) { + + m := new(Mock) + m.On("One", 1).Return("one") + m.On("Two", 2).Return("two") + m.On("Two", 3).Return("three") + + f, c := m.findExpectedCall("Two", 3) + + if assert.Equal(t, 2, f) { + if assert.NotNil(t, c) { + assert.Equal(t, "Two", c.Method) + assert.Equal(t, 3, c.Arguments[0]) + assert.Equal(t, "three", c.ReturnArguments[0]) + } + } + +} + +func Test_Mock_findExpectedCall_For_Unknown_Method(t *testing.T) { + + m := new(Mock) + m.On("One", 1).Return("one") + m.On("Two", 2).Return("two") + m.On("Two", 3).Return("three") + + f, _ := m.findExpectedCall("Two") + + assert.Equal(t, -1, f) + +} + +func Test_Mock_findExpectedCall_Respects_Repeatability(t *testing.T) { + + m := new(Mock) + m.On("One", 1).Return("one") + m.On("Two", 2).Return("two").Once() + m.On("Two", 3).Return("three").Twice() + m.On("Two", 3).Return("three").Times(8) + + f, c := m.findExpectedCall("Two", 3) + + if assert.Equal(t, 2, f) { + if assert.NotNil(t, c) { + assert.Equal(t, "Two", c.Method) + assert.Equal(t, 3, c.Arguments[0]) + assert.Equal(t, "three", c.ReturnArguments[0]) + } + } + +} + +func Test_callString(t *testing.T) { + + assert.Equal(t, `Method(int,bool,string)`, callString("Method", []interface{}{1, true, "something"}, false)) + +} + +func Test_Mock_Called(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_Called", 1, 2, 3).Return(5, "6", true) + + returnArguments := mockedService.Mock.Called(1, 2, 3) + + if assert.Equal(t, 1, len(mockedService.Mock.Calls)) { + assert.Equal(t, "Test_Mock_Called", mockedService.Mock.Calls[0].Method) + assert.Equal(t, 1, mockedService.Mock.Calls[0].Arguments[0]) + assert.Equal(t, 2, mockedService.Mock.Calls[0].Arguments[1]) + assert.Equal(t, 3, mockedService.Mock.Calls[0].Arguments[2]) + } + + if assert.Equal(t, 3, len(returnArguments)) { + assert.Equal(t, 5, returnArguments[0]) + assert.Equal(t, "6", returnArguments[1]) + assert.Equal(t, true, returnArguments[2]) + } + +} + +func Test_Mock_Called_For_Bounded_Repeatability(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_Called_For_Bounded_Repeatability", 1, 2, 3).Return(5, "6", true).Once() + mockedService.Mock.On("Test_Mock_Called_For_Bounded_Repeatability", 1, 2, 3).Return(-1, "hi", false) + + returnArguments1 := mockedService.Mock.Called(1, 2, 3) + returnArguments2 := mockedService.Mock.Called(1, 2, 3) + + if assert.Equal(t, 2, len(mockedService.Mock.Calls)) { + assert.Equal(t, "Test_Mock_Called_For_Bounded_Repeatability", mockedService.Mock.Calls[0].Method) + assert.Equal(t, 1, mockedService.Mock.Calls[0].Arguments[0]) + assert.Equal(t, 2, mockedService.Mock.Calls[0].Arguments[1]) + assert.Equal(t, 3, mockedService.Mock.Calls[0].Arguments[2]) + + assert.Equal(t, "Test_Mock_Called_For_Bounded_Repeatability", mockedService.Mock.Calls[1].Method) + assert.Equal(t, 1, mockedService.Mock.Calls[1].Arguments[0]) + assert.Equal(t, 2, mockedService.Mock.Calls[1].Arguments[1]) + assert.Equal(t, 3, mockedService.Mock.Calls[1].Arguments[2]) + } + + if assert.Equal(t, 3, len(returnArguments1)) { + assert.Equal(t, 5, returnArguments1[0]) + assert.Equal(t, "6", returnArguments1[1]) + assert.Equal(t, true, returnArguments1[2]) + } + + if assert.Equal(t, 3, len(returnArguments2)) { + assert.Equal(t, -1, returnArguments2[0]) + assert.Equal(t, "hi", returnArguments2[1]) + assert.Equal(t, false, returnArguments2[2]) + } + +} + +func Test_Mock_Called_For_SetTime_Expectation(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("TheExampleMethod", 1, 2, 3).Return(5, "6", true).Times(4) + + mockedService.TheExampleMethod(1, 2, 3) + mockedService.TheExampleMethod(1, 2, 3) + mockedService.TheExampleMethod(1, 2, 3) + mockedService.TheExampleMethod(1, 2, 3) + assert.Panics(t, func() { + mockedService.TheExampleMethod(1, 2, 3) + }) + +} + +func Test_Mock_Called_Unexpected(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + // make sure it panics if no expectation was made + assert.Panics(t, func() { + mockedService.Mock.Called(1, 2, 3) + }, "Calling unexpected method should panic") + +} + +func Test_AssertExpectationsForObjects_Helper(t *testing.T) { + + var mockedService1 *TestExampleImplementation = new(TestExampleImplementation) + var mockedService2 *TestExampleImplementation = new(TestExampleImplementation) + var mockedService3 *TestExampleImplementation = new(TestExampleImplementation) + + mockedService1.Mock.On("Test_AssertExpectationsForObjects_Helper", 1).Return() + mockedService2.Mock.On("Test_AssertExpectationsForObjects_Helper", 2).Return() + mockedService3.Mock.On("Test_AssertExpectationsForObjects_Helper", 3).Return() + + mockedService1.Called(1) + mockedService2.Called(2) + mockedService3.Called(3) + + assert.True(t, AssertExpectationsForObjects(t, mockedService1.Mock, mockedService2.Mock, mockedService3.Mock)) + +} + +func Test_AssertExpectationsForObjects_Helper_Failed(t *testing.T) { + + var mockedService1 *TestExampleImplementation = new(TestExampleImplementation) + var mockedService2 *TestExampleImplementation = new(TestExampleImplementation) + var mockedService3 *TestExampleImplementation = new(TestExampleImplementation) + + mockedService1.Mock.On("Test_AssertExpectationsForObjects_Helper_Failed", 1).Return() + mockedService2.Mock.On("Test_AssertExpectationsForObjects_Helper_Failed", 2).Return() + mockedService3.Mock.On("Test_AssertExpectationsForObjects_Helper_Failed", 3).Return() + + mockedService1.Called(1) + mockedService3.Called(3) + + tt := new(testing.T) + assert.False(t, AssertExpectationsForObjects(tt, mockedService1.Mock, mockedService2.Mock, mockedService3.Mock)) + +} + +func Test_Mock_AssertExpectations(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_AssertExpectations", 1, 2, 3).Return(5, 6, 7) + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectations(tt)) + + // make the call now + mockedService.Mock.Called(1, 2, 3) + + // now assert expectations + assert.True(t, mockedService.AssertExpectations(tt)) + +} + +func Test_Mock_AssertExpectationsCustomType(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("TheExampleMethod3", AnythingOfType("*mock.ExampleType")).Return(nil).Once() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectations(tt)) + + // make the call now + mockedService.TheExampleMethod3(&ExampleType{}) + + // now assert expectations + assert.True(t, mockedService.AssertExpectations(tt)) + +} + +func Test_Mock_AssertExpectations_With_Repeatability(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_AssertExpectations_With_Repeatability", 1, 2, 3).Return(5, 6, 7).Twice() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectations(tt)) + + // make the call now + mockedService.Mock.Called(1, 2, 3) + + assert.False(t, mockedService.AssertExpectations(tt)) + + mockedService.Mock.Called(1, 2, 3) + + // now assert expectations + assert.True(t, mockedService.AssertExpectations(tt)) + +} + +func Test_Mock_TwoCallsWithDifferentArguments(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_TwoCallsWithDifferentArguments", 1, 2, 3).Return(5, 6, 7) + mockedService.Mock.On("Test_Mock_TwoCallsWithDifferentArguments", 4, 5, 6).Return(5, 6, 7) + + args1 := mockedService.Mock.Called(1, 2, 3) + assert.Equal(t, 5, args1.Int(0)) + assert.Equal(t, 6, args1.Int(1)) + assert.Equal(t, 7, args1.Int(2)) + + args2 := mockedService.Mock.Called(4, 5, 6) + assert.Equal(t, 5, args2.Int(0)) + assert.Equal(t, 6, args2.Int(1)) + assert.Equal(t, 7, args2.Int(2)) + +} + +func Test_Mock_AssertNumberOfCalls(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_AssertNumberOfCalls", 1, 2, 3).Return(5, 6, 7) + + mockedService.Mock.Called(1, 2, 3) + assert.True(t, mockedService.AssertNumberOfCalls(t, "Test_Mock_AssertNumberOfCalls", 1)) + + mockedService.Mock.Called(1, 2, 3) + assert.True(t, mockedService.AssertNumberOfCalls(t, "Test_Mock_AssertNumberOfCalls", 2)) + +} + +func Test_Mock_AssertCalled(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_AssertCalled", 1, 2, 3).Return(5, 6, 7) + + mockedService.Mock.Called(1, 2, 3) + + assert.True(t, mockedService.AssertCalled(t, "Test_Mock_AssertCalled", 1, 2, 3)) + +} + +func Test_Mock_AssertCalled_WithAnythingOfTypeArgument(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_AssertCalled_WithAnythingOfTypeArgument", Anything, Anything, Anything).Return() + + mockedService.Mock.Called(1, "two", []uint8("three")) + + assert.True(t, mockedService.AssertCalled(t, "Test_Mock_AssertCalled_WithAnythingOfTypeArgument", AnythingOfType("int"), AnythingOfType("string"), AnythingOfType("[]uint8"))) + +} + +func Test_Mock_AssertCalled_WithArguments(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_AssertCalled_WithArguments", 1, 2, 3).Return(5, 6, 7) + + mockedService.Mock.Called(1, 2, 3) + + tt := new(testing.T) + assert.True(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments", 1, 2, 3)) + assert.False(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments", 2, 3, 4)) + +} + +func Test_Mock_AssertCalled_WithArguments_With_Repeatability(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_AssertCalled_WithArguments_With_Repeatability", 1, 2, 3).Return(5, 6, 7).Once() + mockedService.Mock.On("Test_Mock_AssertCalled_WithArguments_With_Repeatability", 2, 3, 4).Return(5, 6, 7).Once() + + mockedService.Mock.Called(1, 2, 3) + mockedService.Mock.Called(2, 3, 4) + + tt := new(testing.T) + assert.True(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments_With_Repeatability", 1, 2, 3)) + assert.True(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments_With_Repeatability", 2, 3, 4)) + assert.False(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments_With_Repeatability", 3, 4, 5)) + +} + +func Test_Mock_AssertNotCalled(t *testing.T) { + + var mockedService *TestExampleImplementation = new(TestExampleImplementation) + + mockedService.Mock.On("Test_Mock_AssertNotCalled", 1, 2, 3).Return(5, 6, 7) + + mockedService.Mock.Called(1, 2, 3) + + assert.True(t, mockedService.AssertNotCalled(t, "Test_Mock_NotCalled")) + +} + +/* + Arguments helper methods +*/ +func Test_Arguments_Get(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true} + + assert.Equal(t, "string", args.Get(0).(string)) + assert.Equal(t, 123, args.Get(1).(int)) + assert.Equal(t, true, args.Get(2).(bool)) + +} + +func Test_Arguments_Is(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true} + + assert.True(t, args.Is("string", 123, true)) + assert.False(t, args.Is("wrong", 456, false)) + +} + +func Test_Arguments_Diff(t *testing.T) { + + var args Arguments = []interface{}{"Hello World", 123, true} + var diff string + var count int + diff, count = args.Diff([]interface{}{"Hello World", 456, "false"}) + + assert.Equal(t, 2, count) + assert.Contains(t, diff, `%!s(int=456) != %!s(int=123)`) + assert.Contains(t, diff, `false != %!s(bool=true)`) + +} + +func Test_Arguments_Diff_DifferentNumberOfArgs(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true} + var diff string + var count int + diff, count = args.Diff([]interface{}{"string", 456, "false", "extra"}) + + assert.Equal(t, 3, count) + assert.Contains(t, diff, `extra != (Missing)`) + +} + +func Test_Arguments_Diff_WithAnythingArgument(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true} + var count int + _, count = args.Diff([]interface{}{"string", Anything, true}) + + assert.Equal(t, 0, count) + +} + +func Test_Arguments_Diff_WithAnythingArgument_InActualToo(t *testing.T) { + + var args Arguments = []interface{}{"string", Anything, true} + var count int + _, count = args.Diff([]interface{}{"string", 123, true}) + + assert.Equal(t, 0, count) + +} + +func Test_Arguments_Diff_WithAnythingOfTypeArgument(t *testing.T) { + + var args Arguments = []interface{}{"string", AnythingOfType("int"), true} + var count int + _, count = args.Diff([]interface{}{"string", 123, true}) + + assert.Equal(t, 0, count) + +} + +func Test_Arguments_Diff_WithAnythingOfTypeArgument_Failing(t *testing.T) { + + var args Arguments = []interface{}{"string", AnythingOfType("string"), true} + var count int + var diff string + diff, count = args.Diff([]interface{}{"string", 123, true}) + + assert.Equal(t, 1, count) + assert.Contains(t, diff, `string != type int - %!s(int=123)`) + +} + +func Test_Arguments_Assert(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true} + + assert.True(t, args.Assert(t, "string", 123, true)) + +} + +func Test_Arguments_String_Representation(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true} + assert.Equal(t, `string,int,bool`, args.String()) + +} + +func Test_Arguments_String(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true} + assert.Equal(t, "string", args.String(0)) + +} + +func Test_Arguments_Error(t *testing.T) { + + var err error = errors.New("An Error") + var args Arguments = []interface{}{"string", 123, true, err} + assert.Equal(t, err, args.Error(3)) + +} + +func Test_Arguments_Error_Nil(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true, nil} + assert.Equal(t, nil, args.Error(3)) + +} + +func Test_Arguments_Int(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true} + assert.Equal(t, 123, args.Int(1)) + +} + +func Test_Arguments_Bool(t *testing.T) { + + var args Arguments = []interface{}{"string", 123, true} + assert.Equal(t, true, args.Bool(2)) + +} diff --git a/third_party/src/github.com/stretchr/testify/package_test.go b/third_party/src/github.com/stretchr/testify/package_test.go new file mode 100644 index 00000000000..7ac5d6d8edb --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/package_test.go @@ -0,0 +1,12 @@ +package testify + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestImports(t *testing.T) { + if assert.Equal(t, 1, 1) != true { + t.Error("Something is wrong.") + } +} diff --git a/third_party/src/github.com/stretchr/testify/suite/doc.go b/third_party/src/github.com/stretchr/testify/suite/doc.go new file mode 100644 index 00000000000..cc0b7625348 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/suite/doc.go @@ -0,0 +1,63 @@ +// The suite package contains logic for creating testing suite structs +// and running the methods on those structs as tests. The most useful +// piece of this package is that you can create setup/teardown methods +// on your testing suites, which will run before/after the whole suite +// or individual tests (depending on which interface(s) you +// implement). +// +// A testing suite is usually built by first extending the built-in +// suite functionality from suite.Suite in testify. Alternatively, +// you could reproduce that logic on your own if you wanted (you +// just need to implement the TestingSuite interface from +// suite/interfaces.go). +// +// After that, you can implement any of the interfaces in +// suite/interfaces.go to add setup/teardown functionality to your +// suite, and add any methods that start with "Test" to add tests. +// Methods that do not match any suite interfaces and do not begin +// with "Test" will not be run by testify, and can safely be used as +// helper methods. +// +// Once you've built your testing suite, you need to run the suite +// (using suite.Run from testify) inside any function that matches the +// identity that "go test" is already looking for (i.e. +// func(*testing.T)). +// +// Regular expression to select test suites specified command-line +// argument "-run". Regular expression to select the methods +// of test suites specified command-line argument "-m". +// +// A crude example: +// // Basic imports +// import ( +// "testing" +// "github.com/stretchr/testify/assert" +// "github.com/stretchr/testify/suite" +// ) +// +// // Define the suite, and absorb the built-in basic suite +// // functionality from testify - including a T() method which +// // returns the current testing context +// type ExampleTestSuite struct { +// suite.Suite +// VariableThatShouldStartAtFive int +// } +// +// // Make sure that VariableThatShouldStartAtFive is set to five +// // before each test +// func (suite *ExampleTestSuite) SetupTest() { +// suite.VariableThatShouldStartAtFive = 5 +// } +// +// // All methods that begin with "Test" are run as tests within a +// // suite. +// func (suite *ExampleTestSuite) TestExample() { +// assert.Equal(suite.T(), suite.VariableThatShouldStartAtFive, 5) +// } +// +// // In order for 'go test' to run this suite, we need to create +// // a normal test function and pass our suite to suite.Run +// func TestExampleTestSuite(t *testing.T) { +// suite.Run(t, new(ExampleTestSuite)) +// } +package suite diff --git a/third_party/src/github.com/stretchr/testify/suite/interfaces.go b/third_party/src/github.com/stretchr/testify/suite/interfaces.go new file mode 100644 index 00000000000..20969472c7d --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/suite/interfaces.go @@ -0,0 +1,34 @@ +package suite + +import "testing" + +// TestingSuite can store and return the current *testing.T context +// generated by 'go test'. +type TestingSuite interface { + T() *testing.T + SetT(*testing.T) +} + +// SetupAllSuite has a SetupSuite method, which will run before the +// tests in the suite are run. +type SetupAllSuite interface { + SetupSuite() +} + +// SetupTestSuite has a SetupTest method, which will run before each +// test in the suite. +type SetupTestSuite interface { + SetupTest() +} + +// TearDownAllSuite has a TearDownSuite method, which will run after +// all the tests in the suite have been run. +type TearDownAllSuite interface { + TearDownSuite() +} + +// TearDownTestSuite has a TearDownTest method, which will run after +// each test in the suite. +type TearDownTestSuite interface { + TearDownTest() +} diff --git a/third_party/src/github.com/stretchr/testify/suite/suite.go b/third_party/src/github.com/stretchr/testify/suite/suite.go new file mode 100644 index 00000000000..17d24cd0890 --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/suite/suite.go @@ -0,0 +1,85 @@ +package suite + +import ( + "flag" + "fmt" + "os" + "reflect" + "regexp" + "testing" +) + +var matchMethod = flag.String("m", "", "regular expression to select tests of the suite to run") + +// Suite is a basic testing suite with methods for storing and +// retrieving the current *testing.T context. +type Suite struct { + t *testing.T +} + +// T retrieves the current *testing.T context. +func (suite *Suite) T() *testing.T { + return suite.t +} + +// SetT sets the current *testing.T context. +func (suite *Suite) SetT(t *testing.T) { + suite.t = t +} + +// Run takes a testing suite and runs all of the tests attached +// to it. +func Run(t *testing.T, suite TestingSuite) { + suite.SetT(t) + + if setupAllSuite, ok := suite.(SetupAllSuite); ok { + setupAllSuite.SetupSuite() + } + + methodFinder := reflect.TypeOf(suite) + tests := []testing.InternalTest{} + for index := 0; index < methodFinder.NumMethod(); index++ { + method := methodFinder.Method(index) + ok, err := methodFilter(method.Name) + if err != nil { + fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err) + os.Exit(1) + } + if ok { + test := testing.InternalTest{ + Name: method.Name, + F: func(t *testing.T) { + parentT := suite.T() + suite.SetT(t) + if setupTestSuite, ok := suite.(SetupTestSuite); ok { + setupTestSuite.SetupTest() + } + method.Func.Call([]reflect.Value{reflect.ValueOf(suite)}) + if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok { + tearDownTestSuite.TearDownTest() + } + suite.SetT(parentT) + }, + } + tests = append(tests, test) + } + } + + if !testing.RunTests(func(_, _ string) (bool, error) { return true, nil }, + tests) { + t.Fail() + } + + if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok { + tearDownAllSuite.TearDownSuite() + } +} + +// Filtering method according to set regular expression +// specified command-line argument -m +func methodFilter(name string) (bool, error) { + if ok, _ := regexp.MatchString("^Test", name); !ok { + return false, nil + } + return regexp.MatchString(*matchMethod, name) +} diff --git a/third_party/src/github.com/stretchr/testify/suite/suite_test.go b/third_party/src/github.com/stretchr/testify/suite/suite_test.go new file mode 100644 index 00000000000..af01da087fb --- /dev/null +++ b/third_party/src/github.com/stretchr/testify/suite/suite_test.go @@ -0,0 +1,157 @@ +package suite + +import ( + "errors" + "github.com/stretchr/testify/assert" + "io/ioutil" + "os" + "testing" +) + +// This suite is intended to store values to make sure that only +// testing-suite-related methods are run. It's also a fully +// functional example of a testing suite, using setup/teardown methods +// and a helper method that is ignored by testify. To make this look +// more like a real world example, all tests in the suite perform some +// type of assertion. +type SuiteTester struct { + // Include our basic suite logic. + Suite + + // Keep counts of how many times each method is run. + SetupSuiteRunCount int + TearDownSuiteRunCount int + SetupTestRunCount int + TearDownTestRunCount int + TestOneRunCount int + TestTwoRunCount int + NonTestMethodRunCount int +} + +// The SetupSuite method will be run by testify once, at the very +// start of the testing suite, before any tests are run. +func (suite *SuiteTester) SetupSuite() { + suite.SetupSuiteRunCount++ +} + +// The TearDownSuite method will be run by testify once, at the very +// end of the testing suite, after all tests have been run. +func (suite *SuiteTester) TearDownSuite() { + suite.TearDownSuiteRunCount++ +} + +// The SetupTest method will be run before every test in the suite. +func (suite *SuiteTester) SetupTest() { + suite.SetupTestRunCount++ +} + +// The TearDownTest method will be run after every test in the suite. +func (suite *SuiteTester) TearDownTest() { + suite.TearDownTestRunCount++ +} + +// Every method in a testing suite that begins with "Test" will be run +// as a test. TestOne is an example of a test. For the purposes of +// this example, we've included assertions in the tests, since most +// tests will issue assertions. +func (suite *SuiteTester) TestOne() { + beforeCount := suite.TestOneRunCount + suite.TestOneRunCount++ + assert.Equal(suite.T(), suite.TestOneRunCount, beforeCount + 1) +} + +// TestTwo is another example of a test. +func (suite *SuiteTester) TestTwo() { + beforeCount := suite.TestTwoRunCount + suite.TestTwoRunCount++ + assert.NotEqual(suite.T(), suite.TestTwoRunCount, beforeCount) +} + +// NonTestMethod does not begin with "Test", so it will not be run by +// testify as a test in the suite. This is useful for creating helper +// methods for your tests. +func (suite *SuiteTester) NonTestMethod() { + suite.NonTestMethodRunCount++ +} + +// TestRunSuite will be run by the 'go test' command, so within it, we +// can run our suite using the Run(*testing.T, TestingSuite) function. +func TestRunSuite(t *testing.T) { + suiteTester := new(SuiteTester) + Run(t, suiteTester) + + // Normally, the test would end here. The following are simply + // some assertions to ensure that the Run function is working as + // intended - they are not part of the example. + + // The suite was only run once, so the SetupSuite and TearDownSuite + // methods should have each been run only once. + assert.Equal(t, suiteTester.SetupSuiteRunCount, 1) + assert.Equal(t, suiteTester.TearDownSuiteRunCount, 1) + + // There are two test methods (TestOne and TestTwo), so the + // SetupTest and TearDownTest methods (which should be run once for + // each test) should have been run twice. + assert.Equal(t, suiteTester.SetupTestRunCount, 2) + assert.Equal(t, suiteTester.TearDownTestRunCount, 2) + + // Each test should have been run once. + assert.Equal(t, suiteTester.TestOneRunCount, 1) + assert.Equal(t, suiteTester.TestTwoRunCount, 1) + + // Methods that don't match the test method identifier shouldn't + // have been run at all. + assert.Equal(t, suiteTester.NonTestMethodRunCount, 0) +} + +type SuiteLoggingTester struct { + Suite +} + +func (s *SuiteLoggingTester) TestLoggingPass() { + s.T().Log("TESTLOGPASS") +} + +func (s *SuiteLoggingTester) TestLoggingFail() { + s.T().Log("TESTLOGFAIL") + assert.NotNil(s.T(), nil) //expected to fail +} + +type StdoutCapture struct { + oldStdout *os.File + readPipe *os.File +} + +func (sc *StdoutCapture) StartCapture() { + sc.oldStdout = os.Stdout + sc.readPipe, os.Stdout, _ = os.Pipe() +} + +func (sc *StdoutCapture) StopCapture() (string, error) { + if sc.oldStdout == nil || sc.readPipe == nil { + return "", errors.New("StartCapture not called before StopCapture") + } + os.Stdout.Close() + os.Stdout = sc.oldStdout + bytes, err := ioutil.ReadAll(sc.readPipe) + if err != nil { + return "", err + } + return string(bytes), nil +} + +func TestSuiteLogging(t *testing.T) { + testT := testing.T{} + + suiteLoggingTester := new(SuiteLoggingTester) + + capture := StdoutCapture{} + capture.StartCapture() + Run(&testT, suiteLoggingTester) + output, err := capture.StopCapture() + + assert.Nil(t, err, "Got an error trying to capture stdout!") + + assert.Contains(t, output, "TESTLOGFAIL") + assert.NotContains(t, output, "TESTLOGPASS") +}