cadvisor-0.27.1/000077500000000000000000000000001315410276000134125ustar00rootroot00000000000000cadvisor-0.27.1/.gitignore000066400000000000000000000001731315410276000154030ustar00rootroot00000000000000cadvisor /release .vscode # Go test binaries *.test # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA .idea/ *.iml cadvisor-0.27.1/.travis.yml000066400000000000000000000003171315410276000155240ustar00rootroot00000000000000language: go sudo: false go: - 1.5.3 install: - true before_script: - go get golang.org/x/tools/cmd/vet - go get github.com/kr/godep script: - export PATH=$PATH:$HOME/gopath/bin - ./build/presubmit.sh cadvisor-0.27.1/AUTHORS000066400000000000000000000004001315410276000144540ustar00rootroot00000000000000# This is the official list of cAdvisor authors for copyright purposes. # 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. cadvisor-0.27.1/CHANGELOG.md000066400000000000000000000230551315410276000152300ustar00rootroot00000000000000# Changelog ### 0.27.1 (2017-09-06) - Add CRI-O support ### 0.27.0 (2017-09-01) - Fix journalctl leak - Fix container memory rss - Add hugepages support - Fix incorrect CPU usage with 4.7 kernel - OOM parser uses kmsg - Add tmpfs support ### 0.26.1 (2017-06-21) - Fix prometheus metrics. ### 0.26.0 (2017-05-31) - Fix disk partition discovery for brtfs - Add ZFS support - Add UDP metrics (collection disabled by default) - Improve diskio prometheus metrics - Update Prometheus godeps to v0.8 - Add overlay2 storage driver support ### 0.25.0 (2017-03-09) - Disable thin_ls due to excessive iops - Ignore .mount cgroups, fixing dissappearing stats - Fix wc goroutine leak - Update aws-sdk-go dependency to 1.6.10 - Update to go 1.7 for releases ### 0.24.1 (2016-10-10) - Fix issue with running cAdvisor in a container on some distributions. ### 0.24.0 (2016-09-19) - Added host-level inode stats (total & available) - Improved robustness to partial failures - Metrics collector improvements - Added ability to directly use endpoints from the container itself - Allow SSL endpoint access - Ability to provide a certificate which is exposed to custom endpoints - Lots of bug fixes, including: - Devicemapper thin_ls fixes - Prometheus metrics fixes - Fixes for missing stats (memory reservation, FS usage, etc.) ### 0.23.9 (2016-08-09) - Cherry-pick release: - Ensure minimum kernel version for thin_ls ### 0.23.8 (2016-08-02) - Cherry-pick release: - Prefix Docker labels & env vars in Prometheus metrics to prevent conflicts ### 0.23.7 (2016-07-18) - Cherry-pick release: - Modify working set memory stats calculation ### 0.23.6 (2016-06-23) - Cherry-pick release: - Updating inotify to fix memory leak v0.23 cherrypick ### 0.23.5 (2016-06-22) - Cherry-pick release: - support LVM based device mapper storage drivers ### 0.23.4 (2016-06-16) - Cherry-pick release: - Check for thin_is binary in path for devicemapper when using ThinPoolWatcher - Fix uint64 overflow issue for CPU stats ### 0.23.3 (2016-06-08) - Cherry-pick release: - Cap the maximum consecutive du commands - Fix a panic when a prometheus endpoint ends with a newline ### 0.23.2 (2016-05-18) - Handle kernel log rotation - More rkt support: poll rkt service for new containers - Better handling of partial failures when fetching subcontainers - Devicemapper thin_ls support (requires Device Mapper kernel module and supporting utilities) ### 0.23.1 (2016-05-11) - Add multi-container charts to the UI - Add TLS options for Kafka storage driver - Switch to official Docker client - Systemd: - Ignore .mount cgroups on systemd - Better OOM monitoring - Bug: Fix broken -disable_metrics flag - Bug: Fix openstack identified as AWS - Bug: Fix EventStore when limit is 0 ### 0.23.0 (2016-04-21) - Docker v1.11 support - Preliminary rkt support - Bug: Fix file descriptor leak ### 0.22.0 (2016-02-25) - Disk usage calculation bug fixes - Systemd integration bug fixes - Instance ID support for Azure and AWS - Limit number of custom metrics - Support opt out for disk and network metrics ### 0.21.0 (2016-02-03) - Support for filesystem stats with docker v1.10 - Bug fixes. ### 0.20.5 (2016-01-27) - Breaking: Use uint64 for memory stats - Bug: Fix devicemapper partition labelling - Bug: Fix network stats when using new Docker network functionality - Bug: Fix env var label mapping initialization - Dependencies: libcontainer update ### 0.20.4 (2016-01-20) - Godep updates ### 0.20.3 (2016-01-19) - Bug fixes - Jitter added to housekeeping to smooth CPU usage. ### 0.20.2 (2016-01-15) - New v2.1 API with better filesystem stats - Internal refactoring - Bug fixes. ### 0.18.0 (2015-09-23) - Large bunch of bug-fixes - Fixed networking stats for newer docker versions using libnetwork. - Added application-specific metrics ## 0.16.0 (2015-06-26) - Misc fixes. ## 0.15.1 (2015-06-10) - Fix longstanding memory leak. - Fix UI on newest Chrome. ## 0.15.0 (2015-06-08) - Expose multiple network intefaces in UI and API. - Add support for XFS. - Fixes in inotify watches. - Fixes on PowerPC machines. - Fixes for newer systems with systemd. - Extra debuging informaiton in /validate. ## 0.14.0 (2015-05-21) - Add process stats to container pages in the UI. - Serve UI from relative paths (allows reverse proxying). - Minor fixes to events API. - Add bytes available to FS info. - Adding Docker status and image information to UI. - Basic Redis storage backend. - Misc reliability improvements. ## 0.13.0 (2015-05-01) - Added `--docker_only` to limit monitoring to only Docker containers. - Added support for Docker labels. - Added limit for events storage. - Fixes for OOM event monitoring. - Changed event type to a string in the API. - Misc fixes. ## 0.12.0 (2015-04-15) - Added support for Docker 1.6. - Split OOM event into OOM kill and OOM. - Made EventData a concrete type in returned events. - Enabled CPU load tracking (experimental). ## 0.11.0 (2015-03-27) - Export all stats as [Prometheus](https://prometheus.io/) metrics. - Initial support for [events](docs/api.md): creation, deletion, and OOM. - Adding machine UUID information. - Beta release of the cAdvisor [2.0 API](docs/api_v2.md). - Improve handling of error conditions. - Misc fixes and improvements. ## 0.10.1 (2015-02-27) - Disable OOM monitoring which is using too much CPU. - Fix break in summary stats. ## 0.10.0 (2015-02-24) - Adding Start and End time for ContainerInfoRequest. - Various misc fixes. ## 0.9.0 (2015-02-06) - Support for more network devices (all non-eth). - Support for more partition types (btrfs, device-mapper, whole-disk). - Added reporting of DiskIO stats. - Adding container creation time to ContainerSpec. - More robust handling of stats failures. - Various misc fixes. ## 0.8.0 (2015-01-09) - Added ethernet device information. - Added machine-wide networking statistics. - Misc UI fixes. - Fixes for partially-isolated containers. ## 0.7.1 (2014-12-23) - Avoid repeated logging of container errors. - Handle non identify mounts for cgroups. ## 0.7.0 (2014-12-18) - Support for HTTP basic auth. - Added /validate to perform basic checks and determine support for cAdvisor. - All stats in the UI are now updated. - Added gauges for filesystem usage. - Added device information to machine info. - Fixes to container detection. - Fixes for systemd detection. - ContainerSpecs are now cached. - Performance improvements. ## 0.6.2 (2014-11-20) - Fixes for Docker API and UI endpoints. - Misc UI bugfixes. ## 0.6.1 (2014-11-18) - Bug fix in InfluxDB storage driver. Container name and hostname will be exported. ## 0.6.0 (2014-11-17) - Adding /docker UI endpoint for Docker containers. - Fixes around handling Docker containers. - Performance enhancements. - Embed all external dependencies. - ContainerStats Go struct has been flattened. The wire format remains unchanged. - Misc bugfixes and cleanups. ## 0.5.0 (2014-10-28) - Added disk space stats. On by default for root, available on AUFS Docker containers. - Introduced v1.2 remote API with new "docker" resource for Docker containers. - Added "ContainerHints" file based interface to inject extra information about containers. ## 0.4.1 (2014-09-29) - Support for Docker containers in systemd systems. - Adding DiskIO stats - Misc bugfixes and cleanups ## 0.4.0 (2014-09-19) - Various performance enhancements: brings CPU usage down 85%+ - Implemented dynamic sampling through dynamic housekeeping. - Memory storage driver is always on, BigQuery and InfluxDB are now optional storage backends. - Fix for DNS resolution crashes when contacting InfluxDB. - New containers are now detected using inotify. - Added pprof HTTP endpoint. - UI bugfixes. ## 0.3.0 (2014-09-05) - Support for Docker with LXC backend. - Added BigQuery storage driver. - Performance and stability fixes for InfluxDB storage driver. - UI fixes and improvements. - Configurable stats gathering interval (default: 1s). - Improvements to startup and CPU utilization. - Added /healthz endpoint for determining whether cAdvisor is healthy. - Bugfixes and performance improvements. ## 0.2.2 (2014-08-13) - Improvements to influxDB plugin. Table name is now 'stats'. Network stats added. Detailed cpu and memory stats are no longer exported to reduce the load on the DB. Docker container alias now exported - It is now possible to aggregate stats across multiple nodes. - Make the UI independent of the storage backend by caching recent stats in memory. - Switched to glog. - Bugfixes and performance improvements. - Introduced v1.1 remote API with new "subcontainers" resource. ## 0.2.1 (2014-07-25) - Handle old Docker versions. - UI fixes and other bugfixes. ## 0.2.0 (2014-07-24) - Added network stats to the UI. - Added support for CoreOS and RHEL. - Bugfixes and reliability fixes. ## 0.1.4 (2014-07-22) - Add network statistics to REST API. - Add "raw" driver to handle non-Docker containers. - Remove lmctfy in favor of the raw driver. - Bugfixes for Docker containers and logging. ## 0.1.3 (2014-07-14) - Add support for systemd systems. - Fixes for UI with InfluxDB storage driver. ## 0.1.2 (2014-07-10) - Added Storage Driver concept (flag: storage_driver), default is the in-memory driver - Implemented InfluxDB storage driver - Support in REST API for specifying number of stats to return - Allow running without lmctfy (flag: allow_lmctfy) - Bugfixes ## 0.1.0 (2014-06-14) - Support for container aliases - Sampling historical usage and exporting that in the REST API - Bugfixes for UI ## 0.0.0 (2014-06-10) - Initial version of cAdvisor - Web UI with auto-updating stats - v1.0 REST API with container and machine information - Support for Docker containers - Support for lmctfy containers cadvisor-0.27.1/CONTRIBUTING.md000066400000000000000000000015501315410276000156440ustar00rootroot00000000000000## 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. cadvisor-0.27.1/Godeps/000077500000000000000000000000001315410276000146335ustar00rootroot00000000000000cadvisor-0.27.1/Godeps/Godeps.json000066400000000000000000000475541315410276000167660ustar00rootroot00000000000000{ "ImportPath": "github.com/google/cadvisor", "GoVersion": "go1.8", "GodepVersion": "v79", "Packages": [ "./..." ], "Deps": [ { "ImportPath": "cloud.google.com/go/compute/metadata", "Comment": "v0.1.0-115-g3b1ae45", "Rev": "3b1ae45394a234c385be014e9a488f2bb6eef821" }, { "ImportPath": "cloud.google.com/go/internal", "Comment": "v0.1.0-115-g3b1ae45", "Rev": "3b1ae45394a234c385be014e9a488f2bb6eef821" }, { "ImportPath": "github.com/Microsoft/go-winio", "Comment": "v0.3.2", "Rev": "862b6557927a5c5c81e411c12aa6de7e566cbb7a" }, { "ImportPath": "github.com/SeanDolphin/bqschema", "Rev": "f92a08f515e1bf718e995076a37c2f534b1deb08" }, { "ImportPath": "github.com/Shopify/sarama", "Comment": "v1.8.0", "Rev": "4ba9bba6adb6697bcec3841e1ecdfecf5227c3b9" }, { "ImportPath": "github.com/abbot/go-http-auth", "Rev": "c0ef4539dfab4d21c8ef20ba2924f9fc6f186d35" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/awserr", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/client", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/credentials", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/stscreds", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/defaults", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/endpoints", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/request", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/session", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/aws/signer/v4", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/aws/aws-sdk-go/service/sts", "Comment": "v1.6.10", "Rev": "63ce630574a5ec05ecd8e8de5cea16332a5a684d" }, { "ImportPath": "github.com/beorn7/perks/quantile", "Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d" }, { "ImportPath": "github.com/blang/semver", "Comment": "v3.1.0", "Rev": "aea32c919a18e5ef4537bbd283ff29594b1b0165" }, { "ImportPath": "github.com/coreos/go-systemd/dbus", "Comment": "v8-2-g4484981", "Rev": "4484981625c1a6a2ecb40a390fcb6a9bcfee76e3" }, { "ImportPath": "github.com/coreos/go-systemd/util", "Comment": "v8-2-g4484981", "Rev": "4484981625c1a6a2ecb40a390fcb6a9bcfee76e3" }, { "ImportPath": "github.com/coreos/pkg/dlopen", "Comment": "v2", "Rev": "7f080b6c11ac2d2347c3cd7521e810207ea1a041" }, { "ImportPath": "github.com/coreos/rkt/api/v1alpha", "Comment": "v1.6.0", "Rev": "14437382a98e5ebeb6cafb57cff445370e3f7d56" }, { "ImportPath": "github.com/docker/distribution/digestset", "Comment": "v2.6.0-rc.1-209-gedc3ab29", "Rev": "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c" }, { "ImportPath": "github.com/docker/distribution/reference", "Comment": "v2.6.0-rc.1-209-gedc3ab29", "Rev": "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c" }, { "ImportPath": "github.com/docker/docker/pkg/longpath", "Comment": "docs-v1.12.0-rc4-2016-07-15-7415-g8af4db6f0", "Rev": "8af4db6f002ac907b6ef8610b237879dfcaa5b7a" }, { "ImportPath": "github.com/docker/docker/pkg/mount", "Comment": "docs-v1.12.0-rc4-2016-07-15-7415-g8af4db6f0", "Rev": "8af4db6f002ac907b6ef8610b237879dfcaa5b7a" }, { "ImportPath": "github.com/docker/docker/pkg/symlink", "Comment": "docs-v1.12.0-rc4-2016-07-15-7415-g8af4db6f0", "Rev": "8af4db6f002ac907b6ef8610b237879dfcaa5b7a" }, { "ImportPath": "github.com/docker/docker/pkg/system", "Comment": "docs-v1.12.0-rc4-2016-07-15-7415-g8af4db6f0", "Rev": "8af4db6f002ac907b6ef8610b237879dfcaa5b7a" }, { "ImportPath": "github.com/docker/engine-api/client", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/client/transport", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/client/transport/cancellable", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types/blkiodev", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types/container", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types/filters", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types/network", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types/reference", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types/registry", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types/strslice", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types/time", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/engine-api/types/versions", "Comment": "v0.3.1-48-g87b3df2", "Rev": "87b3df23dcba0ce02bfe0474e29a08a97f7814e6" }, { "ImportPath": "github.com/docker/go-connections/nat", "Comment": "v0.2.1-30-g3ede32e", "Rev": "3ede32e2033de7505e6500d6c868c2b9ed9f169d" }, { "ImportPath": "github.com/docker/go-connections/sockets", "Comment": "v0.2.1-30-g3ede32e", "Rev": "3ede32e2033de7505e6500d6c868c2b9ed9f169d" }, { "ImportPath": "github.com/docker/go-connections/tlsconfig", "Comment": "v0.2.1-30-g3ede32e", "Rev": "3ede32e2033de7505e6500d6c868c2b9ed9f169d" }, { "ImportPath": "github.com/docker/go-units", "Comment": "v0.1.0-21-g0bbddae", "Rev": "0bbddae09c5a5419a8c6dcdd7ff90da3d450393b" }, { "ImportPath": "github.com/eapache/go-resiliency/breaker", "Comment": "v1.0.0-4-gb86b1ec", "Rev": "b86b1ec0dd4209a588dc1285cdd471e73525c0b3" }, { "ImportPath": "github.com/eapache/queue", "Comment": "v1.0.2", "Rev": "ded5959c0d4e360646dc9e9908cff48666781367" }, { "ImportPath": "github.com/euank/go-kmsg-parser/kmsgparser", "Comment": "v2.0.0", "Rev": "5ba4d492e455a77d25dcf0d2c4acc9f2afebef4e" }, { "ImportPath": "github.com/garyburd/redigo/internal", "Rev": "535138d7bcd717d6531c701ef5933d98b1866257" }, { "ImportPath": "github.com/garyburd/redigo/redis", "Rev": "535138d7bcd717d6531c701ef5933d98b1866257" }, { "ImportPath": "github.com/go-ini/ini", "Comment": "v1.9.0", "Rev": "193d1ecb466bf97aae8b454a5cfc192941c64809" }, { "ImportPath": "github.com/godbus/dbus", "Comment": "v3", "Rev": "c7fdd8b5cd55e87b4e1f4e372cdb1db61dd6c66f" }, { "ImportPath": "github.com/golang/glog", "Rev": "fca8c8854093a154ff1eb580aae10276ad6b1b5f" }, { "ImportPath": "github.com/golang/protobuf/proto", "Rev": "ab9f9a6dab164b7d1246e0e688b0ab7b94d8553e" }, { "ImportPath": "github.com/golang/snappy", "Rev": "723cc1e459b8eea2dea4583200fd60757d40097a" }, { "ImportPath": "github.com/influxdb/influxdb/client", "Comment": "v0.9.5.1", "Rev": "9eab56311373ee6f788ae5dfc87e2240038f0eb4" }, { "ImportPath": "github.com/influxdb/influxdb/models", "Comment": "v0.9.5.1", "Rev": "9eab56311373ee6f788ae5dfc87e2240038f0eb4" }, { "ImportPath": "github.com/influxdb/influxdb/pkg/escape", "Comment": "v0.9.5.1", "Rev": "9eab56311373ee6f788ae5dfc87e2240038f0eb4" }, { "ImportPath": "github.com/jmespath/go-jmespath", "Comment": "0.2.2-12-g0b12d6b", "Rev": "0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74" }, { "ImportPath": "github.com/klauspost/crc32", "Rev": "a3b15ae34567abb20a22992b989cd76f48d09c47" }, { "ImportPath": "github.com/kr/pretty", "Comment": "go.weekly.2011-12-22-20-g088c856", "Rev": "088c856450c08c03eb32f7a6c221e6eefaa10e6f" }, { "ImportPath": "github.com/kr/text", "Rev": "6807e777504f54ad073ecef66747de158294b639" }, { "ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil", "Rev": "fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a" }, { "ImportPath": "github.com/mistifyio/go-zfs", "Comment": "v2.1.1-5-g1b4ae6f", "Rev": "1b4ae6fb4e77b095934d4430860ff202060169f8" }, { "ImportPath": "github.com/mrunalp/fileutils", "Rev": "4ee1cc9a80582a0c75febdd5cfa779ee4361cbca" }, { "ImportPath": "github.com/opencontainers/go-digest", "Comment": "v1.0.0-rc0-6-g279bed9", "Rev": "279bed98673dd5bef374d3b6e4b09e2af76183bf" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/apparmor", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/cgroups", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/cgroups/fs", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/cgroups/rootless", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/cgroups/systemd", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/configs", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/configs/validate", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/criurpc", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/keys", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/seccomp", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/stacktrace", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/system", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/user", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runc/libcontainer/utils", "Comment": "v1.0.0-rc4-50-g4d6e6720", "Rev": "4d6e6720a7c885c37b4cb083c0d372dda3425120" }, { "ImportPath": "github.com/opencontainers/runtime-spec/specs-go", "Comment": "v1.0.0-rc5-67-gf227620", "Rev": "f2276206b32ad0c2478bfc6440ceb7d51d815cf8" }, { "ImportPath": "github.com/opencontainers/selinux/go-selinux", "Comment": "v1.0.0-rc1-5-g4a2974b", "Rev": "4a2974bf1ee960774ffd517717f1f45325af0206" }, { "ImportPath": "github.com/opencontainers/selinux/go-selinux/label", "Comment": "v1.0.0-rc1-5-g4a2974b", "Rev": "4a2974bf1ee960774ffd517717f1f45325af0206" }, { "ImportPath": "github.com/pborman/uuid", "Rev": "cccd189d45f7ac3368a0d127efb7f4d08ae0b655" }, { "ImportPath": "github.com/pkg/errors", "Comment": "v0.8.0-5-gc605e28", "Rev": "c605e284fe17294bda444b34710735b29d1a9d90" }, { "ImportPath": "github.com/prometheus/client_golang/prometheus", "Comment": "v0.8.0-62-g08fd2e1", "Rev": "08fd2e12372a66e68e30523c7642e0cbc3e4fbde" }, { "ImportPath": "github.com/prometheus/client_golang/prometheus/promhttp", "Comment": "v0.8.0-62-g08fd2e1", "Rev": "08fd2e12372a66e68e30523c7642e0cbc3e4fbde" }, { "ImportPath": "github.com/prometheus/client_model/go", "Comment": "model-0.0.2-14-g6f38060", "Rev": "6f3806018612930941127f2a7c6c453ba2c527d2" }, { "ImportPath": "github.com/prometheus/common/expfmt", "Rev": "49fee292b27bfff7f354ee0f64e1bc4850462edf" }, { "ImportPath": "github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg", "Rev": "49fee292b27bfff7f354ee0f64e1bc4850462edf" }, { "ImportPath": "github.com/prometheus/common/model", "Rev": "49fee292b27bfff7f354ee0f64e1bc4850462edf" }, { "ImportPath": "github.com/prometheus/procfs", "Rev": "1e2146578273cef808354faa16a1922e0b5d6b2f" }, { "ImportPath": "github.com/prometheus/procfs/xfs", "Rev": "1e2146578273cef808354faa16a1922e0b5d6b2f" }, { "ImportPath": "github.com/seccomp/libseccomp-golang", "Rev": "1b506fc7c24eec5a3693cdcbed40d9c226cfc6a1" }, { "ImportPath": "github.com/sirupsen/logrus", "Comment": "v1.0.3-11-g89742ae", "Rev": "89742aefa4b206dcf400792f3bd35b542998eb3b" }, { "ImportPath": "github.com/stretchr/objx", "Rev": "cbeaeb16a013161a98496fad62933b1d21786672" }, { "ImportPath": "github.com/stretchr/testify/assert", "Rev": "8ce79b9f0b77745113f82c17d0756771456ccbd3" }, { "ImportPath": "github.com/stretchr/testify/mock", "Rev": "8ce79b9f0b77745113f82c17d0756771456ccbd3" }, { "ImportPath": "github.com/stretchr/testify/require", "Rev": "8ce79b9f0b77745113f82c17d0756771456ccbd3" }, { "ImportPath": "github.com/syndtr/gocapability/capability", "Rev": "e7cb7fa329f456b3855136a2642b197bad7366ba" }, { "ImportPath": "github.com/vishvananda/netlink", "Rev": "1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270" }, { "ImportPath": "github.com/vishvananda/netlink/nl", "Rev": "1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270" }, { "ImportPath": "golang.org/x/crypto/ssh/terminal", "Rev": "eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035" }, { "ImportPath": "golang.org/x/exp/inotify", "Rev": "292a51b8d262487dab23a588950e8052d63d9113" }, { "ImportPath": "golang.org/x/net/context", "Rev": "5627bad10b821d907ac4dc81fb3535ec7dd81c2b" }, { "ImportPath": "golang.org/x/net/context/ctxhttp", "Rev": "5627bad10b821d907ac4dc81fb3535ec7dd81c2b" }, { "ImportPath": "golang.org/x/net/http2", "Rev": "5627bad10b821d907ac4dc81fb3535ec7dd81c2b" }, { "ImportPath": "golang.org/x/net/http2/hpack", "Rev": "5627bad10b821d907ac4dc81fb3535ec7dd81c2b" }, { "ImportPath": "golang.org/x/net/internal/timeseries", "Rev": "5627bad10b821d907ac4dc81fb3535ec7dd81c2b" }, { "ImportPath": "golang.org/x/net/proxy", "Rev": "5627bad10b821d907ac4dc81fb3535ec7dd81c2b" }, { "ImportPath": "golang.org/x/net/trace", "Rev": "5627bad10b821d907ac4dc81fb3535ec7dd81c2b" }, { "ImportPath": "golang.org/x/oauth2", "Rev": "ca8a464d23d55afd32571475db223e065ffd8a52" }, { "ImportPath": "golang.org/x/oauth2/internal", "Rev": "ca8a464d23d55afd32571475db223e065ffd8a52" }, { "ImportPath": "golang.org/x/oauth2/jws", "Rev": "ca8a464d23d55afd32571475db223e065ffd8a52" }, { "ImportPath": "golang.org/x/oauth2/jwt", "Rev": "ca8a464d23d55afd32571475db223e065ffd8a52" }, { "ImportPath": "golang.org/x/sys/unix", "Rev": "22e23b74325bf60e4cc1ce7bdc157eda0d8b5c13" }, { "ImportPath": "golang.org/x/sys/windows", "Rev": "22e23b74325bf60e4cc1ce7bdc157eda0d8b5c13" }, { "ImportPath": "google.golang.org/api/bigquery/v2", "Rev": "0c2979aeaa5b573e60d3ddffe5ce8dca8df309bd" }, { "ImportPath": "google.golang.org/api/googleapi", "Rev": "0c2979aeaa5b573e60d3ddffe5ce8dca8df309bd" }, { "ImportPath": "google.golang.org/api/googleapi/internal/uritemplates", "Rev": "0c2979aeaa5b573e60d3ddffe5ce8dca8df309bd" }, { "ImportPath": "google.golang.org/grpc", "Rev": "3c4302b7131d2b14bb9c8aff7aa909bc20540145" }, { "ImportPath": "google.golang.org/grpc/codes", "Rev": "3c4302b7131d2b14bb9c8aff7aa909bc20540145" }, { "ImportPath": "google.golang.org/grpc/credentials", "Rev": "3c4302b7131d2b14bb9c8aff7aa909bc20540145" }, { "ImportPath": "google.golang.org/grpc/grpclog", "Rev": "3c4302b7131d2b14bb9c8aff7aa909bc20540145" }, { "ImportPath": "google.golang.org/grpc/metadata", "Rev": "3c4302b7131d2b14bb9c8aff7aa909bc20540145" }, { "ImportPath": "google.golang.org/grpc/naming", "Rev": "3c4302b7131d2b14bb9c8aff7aa909bc20540145" }, { "ImportPath": "google.golang.org/grpc/peer", "Rev": "3c4302b7131d2b14bb9c8aff7aa909bc20540145" }, { "ImportPath": "google.golang.org/grpc/transport", "Rev": "3c4302b7131d2b14bb9c8aff7aa909bc20540145" }, { "ImportPath": "gopkg.in/olivere/elastic.v2", "Comment": "v2.0.12", "Rev": "3cfe88295d20b6299bd935131fc0fd17316405ad" }, { "ImportPath": "gopkg.in/olivere/elastic.v2/uritemplates", "Comment": "v2.0.12", "Rev": "3cfe88295d20b6299bd935131fc0fd17316405ad" } ] } cadvisor-0.27.1/Godeps/Readme000066400000000000000000000002101315410276000157440ustar00rootroot00000000000000This directory tree is generated automatically by godep. Please do not edit. See https://github.com/tools/godep for more information. cadvisor-0.27.1/LICENSE000066400000000000000000000250151315410276000144220ustar00rootroot00000000000000 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 cadvisor-0.27.1/Makefile000066400000000000000000000026531315410276000150600ustar00rootroot00000000000000# Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and GO := go pkgs = $(shell $(GO) list ./... | grep -v vendor) all: presubmit build test test: @echo ">> running tests" @$(GO) test -short -race $(pkgs) test-integration: @./build/integration.sh test-runner: @$(GO) build github.com/google/cadvisor/integration/runner format: @echo ">> formatting code" @$(GO) fmt $(pkgs) vet: @echo ">> vetting code" @$(GO) vet $(pkgs) build: assets @echo ">> building binaries" @./build/build.sh assets: @echo ">> building assets" @./build/assets.sh release: @echo ">> building release binaries" @./build/release.sh docker: @docker build -t cadvisor:$(shell git rev-parse --short HEAD) -f deploy/Dockerfile . presubmit: vet @echo ">> checking go formatting" @./build/check_gofmt.sh @echo ">> checking file boilerplate" @./build/check_boilerplate.sh .PHONY: all build docker format release test test-integration vet presubmit cadvisor-0.27.1/README.md000066400000000000000000000072131315410276000146740ustar00rootroot00000000000000# 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, histograms of complete historical resource usage and network statistics. This data is exported by container and machine-wide. cAdvisor has native support for [Docker](https://github.com/docker/docker) containers and should support just about any other container type out of the box. We strive for support across the board so feel free to open an issue if that is not the case. cAdvisor's container abstraction is based on [lmctfy](https://github.com/google/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, we have a Docker image that includes everything you need to get started. You can run a single cAdvisor to monitor the whole machine. Simply run: ``` sudo docker run \ --volume=/:/rootfs:ro \ --volume=/var/run:/var/run:rw \ --volume=/sys:/sys:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \ --volume=/dev/disk/:/dev/disk:ro \ --publish=8080:8080 \ --detach=true \ --name=cadvisor \ google/cadvisor:latest ``` cAdvisor is now running (in the background) on `http://localhost:8080`. The setup includes directories with Docker state cAdvisor needs to observe. **Note**: If you're running on CentOS, Fedora, RHEL, or are using LXC take a look at our [running instructions](docs/running.md). We have detailed [instructions](docs/running.md#standalone) on running cAdvisor standalone outside of Docker. cAdvisor [running options](docs/runtime_options.md) may also be interesting for advanced usecases. If you want to build your own cAdvisor Docker image see our [deployment](docs/deploy.md) page. ## Building and Testing See the more detailed instructions in the [build page](docs/development/build.md). This includes instructions for building and deploying the cAdvisor Docker image. ## Exporting stats cAdvisor supports exporting stats to various storage plugins. See the [documentation](docs/storage/README.md) for more details and examples. ## Web UI cAdvisor exposes a web UI at its port: `http://:/` See the [documentation](docs/web.md) for more details. ## Remote REST API & Clients cAdvisor exposes its raw and processed stats via a versioned remote REST API. See the API's [documentation](docs/api.md) for more information. There is also an official Go client implementation in the [client](client/) directory. See the [documentation](docs/clients.md) for more information. ## 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 hang out on [Slack](https://kubernetes.slack.com) in the #sig-node channel (get an invitation [here](http://slack.kubernetes.io/)). We also have the [google-containers Google Groups mailing list](https://groups.google.com/forum/#!forum/google-containers). cadvisor-0.27.1/api/000077500000000000000000000000001315410276000141635ustar00rootroot00000000000000cadvisor-0.27.1/api/handler.go000066400000000000000000000155371315410276000161420ustar00rootroot00000000000000// 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 api provides a handler for /api/ package api import ( "encoding/json" "errors" "fmt" "io" "net/http" "path" "regexp" "sort" "strconv" "strings" "time" "github.com/google/cadvisor/events" httpmux "github.com/google/cadvisor/http/mux" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/manager" "github.com/golang/glog" ) const ( apiResource = "/api/" ) func RegisterHandlers(mux httpmux.Mux, m manager.Manager) error { apiVersions := getApiVersions() supportedApiVersions := make(map[string]ApiVersion, len(apiVersions)) for _, v := range apiVersions { supportedApiVersions[v.Version()] = v } mux.HandleFunc(apiResource, func(w http.ResponseWriter, r *http.Request) { err := handleRequest(supportedApiVersions, m, w, r) if err != nil { http.Error(w, err.Error(), 500) } }) return nil } // Captures the API version, requestType [optional], and remaining request [optional]. var apiRegexp = regexp.MustCompile(`/api/([^/]+)/?([^/]+)?(.*)`) const ( apiVersion = iota + 1 apiRequestType apiRequestArgs ) func handleRequest(supportedApiVersions map[string]ApiVersion, m manager.Manager, w http.ResponseWriter, r *http.Request) error { start := time.Now() defer func() { glog.V(4).Infof("Request took %s", time.Since(start)) }() request := r.URL.Path const apiPrefix = "/api" if !strings.HasPrefix(request, apiPrefix) { return fmt.Errorf("incomplete API request %q", request) } // If the request doesn't have an API version, list those. if request == apiPrefix || request == apiResource { versions := make([]string, 0, len(supportedApiVersions)) for v := range supportedApiVersions { versions = append(versions, v) } sort.Strings(versions) http.Error(w, fmt.Sprintf("Supported API versions: %s", strings.Join(versions, ",")), http.StatusBadRequest) return nil } // Verify that we have all the elements we expect: // //[/] requestElements := apiRegexp.FindStringSubmatch(request) if len(requestElements) == 0 { return fmt.Errorf("malformed request %q", request) } version := requestElements[apiVersion] requestType := requestElements[apiRequestType] requestArgs := strings.Split(requestElements[apiRequestArgs], "/") // Check supported versions. versionHandler, ok := supportedApiVersions[version] if !ok { return fmt.Errorf("unsupported API version %q", version) } // If no request type, list possible request types. if requestType == "" { requestTypes := versionHandler.SupportedRequestTypes() sort.Strings(requestTypes) http.Error(w, fmt.Sprintf("Supported request types: %q", strings.Join(requestTypes, ",")), http.StatusBadRequest) return nil } // Trim the first empty element from the request. if len(requestArgs) > 0 && requestArgs[0] == "" { requestArgs = requestArgs[1:] } return versionHandler.HandleRequest(requestType, requestArgs, m, w, r) } func writeResult(res interface{}, w http.ResponseWriter) error { out, err := json.Marshal(res) if err != nil { return fmt.Errorf("failed to marshall response %+v with error: %s", res, err) } w.Header().Set("Content-Type", "application/json") w.Write(out) return nil } func streamResults(eventChannel *events.EventChannel, w http.ResponseWriter, r *http.Request, m manager.Manager) error { cn, ok := w.(http.CloseNotifier) if !ok { return errors.New("could not access http.CloseNotifier") } flusher, ok := w.(http.Flusher) if !ok { return errors.New("could not access http.Flusher") } w.Header().Set("Transfer-Encoding", "chunked") w.WriteHeader(http.StatusOK) flusher.Flush() enc := json.NewEncoder(w) for { select { case <-cn.CloseNotify(): m.CloseEventChannel(eventChannel.GetWatchId()) return nil case ev := <-eventChannel.GetChannel(): err := enc.Encode(ev) if err != nil { glog.Errorf("error encoding message %+v for result stream: %v", ev, err) } flusher.Flush() } } } func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, error) { query := info.DefaultContainerInfoRequest() decoder := json.NewDecoder(body) err := decoder.Decode(&query) if err != nil && err != io.EOF { return nil, fmt.Errorf("unable to decode the json value: %s", err) } return &query, nil } // The user can set any or none of the following arguments in any order // with any twice defined arguments being assigned the first value. // If the value type for the argument is wrong the field will be assumed to be // unassigned // bools: stream, subcontainers, oom_events, creation_events, deletion_events // ints: max_events, start_time (unix timestamp), end_time (unix timestamp) // example r.URL: http://localhost:8080/api/v1.3/events?oom_events=true&stream=true func getEventRequest(r *http.Request) (*events.Request, bool, error) { query := events.NewRequest() stream := false urlMap := r.URL.Query() if val, ok := urlMap["stream"]; ok { newBool, err := strconv.ParseBool(val[0]) if err == nil { stream = newBool } } if val, ok := urlMap["subcontainers"]; ok { newBool, err := strconv.ParseBool(val[0]) if err == nil { query.IncludeSubcontainers = newBool } } eventTypes := map[string]info.EventType{ "oom_events": info.EventOom, "oom_kill_events": info.EventOomKill, "creation_events": info.EventContainerCreation, "deletion_events": info.EventContainerDeletion, } allEventTypes := false if val, ok := urlMap["all_events"]; ok { newBool, err := strconv.ParseBool(val[0]) if err == nil { allEventTypes = newBool } } for opt, eventType := range eventTypes { if allEventTypes { query.EventType[eventType] = true } else if val, ok := urlMap[opt]; ok { newBool, err := strconv.ParseBool(val[0]) if err == nil { query.EventType[eventType] = newBool } } } if val, ok := urlMap["max_events"]; ok { newInt, err := strconv.Atoi(val[0]) if err == nil { query.MaxEventsReturned = int(newInt) } } if val, ok := urlMap["start_time"]; ok { newTime, err := time.Parse(time.RFC3339, val[0]) if err == nil { query.StartTime = newTime } } if val, ok := urlMap["end_time"]; ok { newTime, err := time.Parse(time.RFC3339, val[0]) if err == nil { query.EndTime = newTime } } return query, stream, nil } func getContainerName(request []string) string { return path.Join("/", strings.Join(request, "/")) } cadvisor-0.27.1/api/versions.go000066400000000000000000000345431315410276000163730ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package api import ( "fmt" "net/http" "path" "strconv" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/manager" "github.com/golang/glog" ) const ( containersApi = "containers" subcontainersApi = "subcontainers" machineApi = "machine" machineStatsApi = "machinestats" dockerApi = "docker" summaryApi = "summary" statsApi = "stats" specApi = "spec" eventsApi = "events" storageApi = "storage" attributesApi = "attributes" versionApi = "version" psApi = "ps" customMetricsApi = "appmetrics" ) // Interface for a cAdvisor API version type ApiVersion interface { // Returns the version string. Version() string // List of supported API endpoints. SupportedRequestTypes() []string // Handles a request. The second argument is the parameters after /api// HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error } // Gets all supported API versions. func getApiVersions() []ApiVersion { v1_0 := &version1_0{} v1_1 := newVersion1_1(v1_0) v1_2 := newVersion1_2(v1_1) v1_3 := newVersion1_3(v1_2) v2_0 := newVersion2_0() v2_1 := newVersion2_1(v2_0) return []ApiVersion{v1_0, v1_1, v1_2, v1_3, v2_0, v2_1} } // API v1.0 type version1_0 struct { } func (self *version1_0) Version() string { return "v1.0" } func (self *version1_0) SupportedRequestTypes() []string { return []string{containersApi, machineApi} } func (self *version1_0) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error { switch requestType { case machineApi: glog.V(4).Infof("Api - Machine") // Get the MachineInfo machineInfo, err := m.GetMachineInfo() if err != nil { return err } err = writeResult(machineInfo, w) if err != nil { return err } case containersApi: containerName := getContainerName(request) glog.V(4).Infof("Api - Container(%s)", containerName) // Get the query request. query, err := getContainerInfoRequest(r.Body) if err != nil { return err } // Get the container. cont, err := m.GetContainerInfo(containerName, query) if err != nil { return fmt.Errorf("failed to get container %q with error: %s", containerName, err) } // Only output the container as JSON. err = writeResult(cont, w) if err != nil { return err } default: return fmt.Errorf("unknown request type %q", requestType) } return nil } // API v1.1 type version1_1 struct { baseVersion *version1_0 } // v1.1 builds on v1.0. func newVersion1_1(v *version1_0) *version1_1 { return &version1_1{ baseVersion: v, } } func (self *version1_1) Version() string { return "v1.1" } func (self *version1_1) SupportedRequestTypes() []string { return append(self.baseVersion.SupportedRequestTypes(), subcontainersApi) } func (self *version1_1) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error { switch requestType { case subcontainersApi: containerName := getContainerName(request) glog.V(4).Infof("Api - Subcontainers(%s)", containerName) // Get the query request. query, err := getContainerInfoRequest(r.Body) if err != nil { return err } // Get the subcontainers. containers, err := m.SubcontainersInfo(containerName, query) if err != nil { return fmt.Errorf("failed to get subcontainers for container %q with error: %s", containerName, err) } // Only output the containers as JSON. err = writeResult(containers, w) if err != nil { return err } return nil default: return self.baseVersion.HandleRequest(requestType, request, m, w, r) } } // API v1.2 type version1_2 struct { baseVersion *version1_1 } // v1.2 builds on v1.1. func newVersion1_2(v *version1_1) *version1_2 { return &version1_2{ baseVersion: v, } } func (self *version1_2) Version() string { return "v1.2" } func (self *version1_2) SupportedRequestTypes() []string { return append(self.baseVersion.SupportedRequestTypes(), dockerApi) } func (self *version1_2) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error { switch requestType { case dockerApi: glog.V(4).Infof("Api - Docker(%v)", request) // Get the query request. query, err := getContainerInfoRequest(r.Body) if err != nil { return err } var containers map[string]info.ContainerInfo // map requests for "docker/" to "docker" if len(request) == 1 && len(request[0]) == 0 { request = request[:0] } switch len(request) { case 0: // Get all Docker containers. containers, err = m.AllDockerContainers(query) if err != nil { return fmt.Errorf("failed to get all Docker containers with error: %v", err) } case 1: // Get one Docker container. var cont info.ContainerInfo cont, err = m.DockerContainer(request[0], query) if err != nil { return fmt.Errorf("failed to get Docker container %q with error: %v", request[0], err) } containers = map[string]info.ContainerInfo{ cont.Name: cont, } default: return fmt.Errorf("unknown request for Docker container %v", request) } // Only output the containers as JSON. err = writeResult(containers, w) if err != nil { return err } return nil default: return self.baseVersion.HandleRequest(requestType, request, m, w, r) } } // API v1.3 type version1_3 struct { baseVersion *version1_2 } // v1.3 builds on v1.2. func newVersion1_3(v *version1_2) *version1_3 { return &version1_3{ baseVersion: v, } } func (self *version1_3) Version() string { return "v1.3" } func (self *version1_3) SupportedRequestTypes() []string { return append(self.baseVersion.SupportedRequestTypes(), eventsApi) } func (self *version1_3) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error { switch requestType { case eventsApi: return handleEventRequest(request, m, w, r) default: return self.baseVersion.HandleRequest(requestType, request, m, w, r) } } func handleEventRequest(request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error { query, stream, err := getEventRequest(r) if err != nil { return err } query.ContainerName = path.Join("/", getContainerName(request)) glog.V(4).Infof("Api - Events(%v)", query) if !stream { pastEvents, err := m.GetPastEvents(query) if err != nil { return err } return writeResult(pastEvents, w) } eventChannel, err := m.WatchForEvents(query) if err != nil { return err } return streamResults(eventChannel, w, r, m) } // API v2.0 type version2_0 struct { } func newVersion2_0() *version2_0 { return &version2_0{} } func (self *version2_0) Version() string { return "v2.0" } func (self *version2_0) SupportedRequestTypes() []string { return []string{versionApi, attributesApi, eventsApi, machineApi, summaryApi, statsApi, specApi, storageApi, psApi, customMetricsApi} } func (self *version2_0) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error { opt, err := getRequestOptions(r) if err != nil { return err } switch requestType { case versionApi: glog.V(4).Infof("Api - Version") versionInfo, err := m.GetVersionInfo() if err != nil { return err } return writeResult(versionInfo.CadvisorVersion, w) case attributesApi: glog.V(4).Info("Api - Attributes") machineInfo, err := m.GetMachineInfo() if err != nil { return err } versionInfo, err := m.GetVersionInfo() if err != nil { return err } info := v2.GetAttributes(machineInfo, versionInfo) return writeResult(info, w) case machineApi: glog.V(4).Info("Api - Machine") // TODO(rjnagal): Move machineInfo from v1. machineInfo, err := m.GetMachineInfo() if err != nil { return err } return writeResult(machineInfo, w) case summaryApi: containerName := getContainerName(request) glog.V(4).Infof("Api - Summary for container %q, options %+v", containerName, opt) stats, err := m.GetDerivedStats(containerName, opt) if err != nil { return err } return writeResult(stats, w) case statsApi: name := getContainerName(request) glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt) infos, err := m.GetRequestedContainersInfo(name, opt) if err != nil { if len(infos) == 0 { return err } glog.Errorf("Error calling GetRequestedContainersInfo: %v", err) } contStats := make(map[string][]v2.DeprecatedContainerStats, 0) for name, cinfo := range infos { contStats[name] = v2.DeprecatedStatsFromV1(cinfo) } return writeResult(contStats, w) case customMetricsApi: containerName := getContainerName(request) glog.V(4).Infof("Api - Custom Metrics: Looking for metrics for container %q, options %+v", containerName, opt) infos, err := m.GetContainerInfoV2(containerName, opt) if err != nil { return err } contMetrics := make(map[string]map[string]map[string][]info.MetricValBasic, 0) for _, cinfo := range infos { metrics := make(map[string]map[string][]info.MetricValBasic, 0) for _, contStat := range cinfo.Stats { if len(contStat.CustomMetrics) == 0 { continue } for name, allLabels := range contStat.CustomMetrics { metricLabels := make(map[string][]info.MetricValBasic, 0) for _, metric := range allLabels { if !metric.Timestamp.IsZero() { metVal := info.MetricValBasic{ Timestamp: metric.Timestamp, IntValue: metric.IntValue, FloatValue: metric.FloatValue, } labels := metrics[name] if labels != nil { values := labels[metric.Label] values = append(values, metVal) labels[metric.Label] = values metrics[name] = labels } else { metricLabels[metric.Label] = []info.MetricValBasic{metVal} metrics[name] = metricLabels } } } } } contMetrics[containerName] = metrics } return writeResult(contMetrics, w) case specApi: containerName := getContainerName(request) glog.V(4).Infof("Api - Spec for container %q, options %+v", containerName, opt) specs, err := m.GetContainerSpec(containerName, opt) if err != nil { return err } return writeResult(specs, w) case storageApi: label := r.URL.Query().Get("label") uuid := r.URL.Query().Get("uuid") switch { case uuid != "": fi, err := m.GetFsInfoByFsUUID(uuid) if err != nil { return err } return writeResult(fi, w) case label != "": // Get a specific label. fi, err := m.GetFsInfo(label) if err != nil { return err } return writeResult(fi, w) default: // Get all global filesystems info. fi, err := m.GetFsInfo("") if err != nil { return err } return writeResult(fi, w) } case eventsApi: return handleEventRequest(request, m, w, r) case psApi: // reuse container type from request. // ignore recursive. // TODO(rjnagal): consider count to limit ps output. name := getContainerName(request) glog.V(4).Infof("Api - Spec for container %q, options %+v", name, opt) ps, err := m.GetProcessList(name, opt) if err != nil { return fmt.Errorf("process listing failed: %v", err) } return writeResult(ps, w) default: return fmt.Errorf("unknown request type %q", requestType) } } type version2_1 struct { baseVersion *version2_0 } func newVersion2_1(v *version2_0) *version2_1 { return &version2_1{ baseVersion: v, } } func (self *version2_1) Version() string { return "v2.1" } func (self *version2_1) SupportedRequestTypes() []string { return append([]string{machineStatsApi}, self.baseVersion.SupportedRequestTypes()...) } func (self *version2_1) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error { // Get the query request. opt, err := getRequestOptions(r) if err != nil { return err } switch requestType { case machineStatsApi: glog.V(4).Infof("Api - MachineStats(%v)", request) cont, err := m.GetRequestedContainersInfo("/", opt) if err != nil { if len(cont) == 0 { return err } glog.Errorf("Error calling GetRequestedContainersInfo: %v", err) } return writeResult(v2.MachineStatsFromV1(cont["/"]), w) case statsApi: name := getContainerName(request) glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt) conts, err := m.GetRequestedContainersInfo(name, opt) if err != nil { if len(conts) == 0 { return err } glog.Errorf("Error calling GetRequestedContainersInfo: %v", err) } contStats := make(map[string]v2.ContainerInfo, len(conts)) for name, cont := range conts { if name == "/" { // Root cgroup stats should be exposed as machine stats continue } contStats[name] = v2.ContainerInfo{ Spec: v2.ContainerSpecFromV1(&cont.Spec, cont.Aliases, cont.Namespace), Stats: v2.ContainerStatsFromV1(name, &cont.Spec, cont.Stats), } } return writeResult(contStats, w) default: return self.baseVersion.HandleRequest(requestType, request, m, w, r) } } func getRequestOptions(r *http.Request) (v2.RequestOptions, error) { supportedTypes := map[string]bool{ v2.TypeName: true, v2.TypeDocker: true, } // fill in the defaults. opt := v2.RequestOptions{ IdType: v2.TypeName, Count: 64, Recursive: false, } idType := r.URL.Query().Get("type") if len(idType) != 0 { if !supportedTypes[idType] { return opt, fmt.Errorf("unknown 'type' %q", idType) } opt.IdType = idType } count := r.URL.Query().Get("count") if len(count) != 0 { n, err := strconv.ParseUint(count, 10, 32) if err != nil { return opt, fmt.Errorf("failed to parse 'count' option: %v", count) } opt.Count = int(n) } recursive := r.URL.Query().Get("recursive") if recursive == "true" { opt.Recursive = true } return opt, nil } cadvisor-0.27.1/api/versions_test.go000066400000000000000000000046271315410276000174320ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package api import ( "io" "net/http" "reflect" "testing" "github.com/google/cadvisor/events" info "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/assert" ) // returns an http.Request pointer for an input url test string func makeHTTPRequest(requestURL string, t *testing.T) *http.Request { dummyReader, _ := io.Pipe() r, err := http.NewRequest("GET", requestURL, dummyReader) assert.Nil(t, err) return r } func TestGetEventRequestBasicRequest(t *testing.T) { r := makeHTTPRequest("http://localhost:8080/api/v1.3/events?oom_events=true&stream=false&max_events=20", t) expectedQuery := events.NewRequest() expectedQuery.EventType = map[info.EventType]bool{ info.EventOom: true, } expectedQuery.MaxEventsReturned = 20 receivedQuery, stream, err := getEventRequest(r) if !reflect.DeepEqual(expectedQuery, receivedQuery) { t.Errorf("expected %#v but received %#v", expectedQuery, receivedQuery) } assert.False(t, stream) assert.Nil(t, err) } func TestGetEventEmptyRequest(t *testing.T) { r := makeHTTPRequest("", t) expectedQuery := events.NewRequest() receivedQuery, stream, err := getEventRequest(r) if !reflect.DeepEqual(expectedQuery, receivedQuery) { t.Errorf("expected %#v but received %#v", expectedQuery, receivedQuery) } assert.False(t, stream) assert.Nil(t, err) } func TestGetEventRequestDoubleArgument(t *testing.T) { r := makeHTTPRequest("http://localhost:8080/api/v1.3/events?stream=true&oom_events=true&oom_events=false", t) expectedQuery := events.NewRequest() expectedQuery.EventType = map[info.EventType]bool{ info.EventOom: true, } receivedQuery, stream, err := getEventRequest(r) if !reflect.DeepEqual(expectedQuery, receivedQuery) { t.Errorf("expected %#v but received %#v", expectedQuery, receivedQuery) } assert.True(t, stream) assert.Nil(t, err) } cadvisor-0.27.1/build/000077500000000000000000000000001315410276000145115ustar00rootroot00000000000000cadvisor-0.27.1/build/assets.sh000077500000000000000000000040371315410276000163560ustar00rootroot00000000000000#!/bin/bash # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -e GIT_ROOT=$(dirname "${BASH_SOURCE}")/.. ASSETS_INPUT_DIRS="$GIT_ROOT/pages/assets/js/... $GIT_ROOT/pages/assets/styles/..." ASSETS_OUTPUT_PATH="$GIT_ROOT/pages/static/assets.go" ASSETS_PACKAGE="static" TEMPLATES_INPUT_DIRS="$GIT_ROOT/pages/assets/html/..." TEMPLATES_OUTPUT_PATH="$GIT_ROOT/pages/templates.go" TEMPLATES_PACKAGE="pages" FORCE="${FORCE:-}" # Force assets to be rebuilt if FORCE=true go get -u github.com/jteeuwen/go-bindata/... build_asset () { local package=$1 local output_path=$2 local input_dirs=${@:3} local tmp_output=$(mktemp) local year=$(date +%Y) go-bindata -nometadata -o $output_path -pkg $package $input_dirs cat build/boilerplate/boilerplate.go.txt | sed "s/YEAR/$year/" > "${tmp_output}" echo -e "// generated by build/assets.sh; DO NOT EDIT\n" >> "${tmp_output}" cat "${output_path}" >> "${tmp_output}" gofmt -w -s "${tmp_output}" mv "${tmp_output}" "${output_path}" } for f in $GIT_ROOT/pages/assets/js/* $GIT_ROOT/pages/assets/styles/*; do if [ "$FORCE" == "true" ] || [ "$f" -nt $ASSETS_OUTPUT_PATH -o ! -e $ASSETS_OUTPUT_PATH ]; then build_asset "$ASSETS_PACKAGE" "$ASSETS_OUTPUT_PATH" "$ASSETS_INPUT_DIRS" break; fi done for f in $GIT_ROOT/pages/assets/html/*; do if [ "$FORCE" == "true" ] || [ "$f" -nt $TEMPLATES_OUTPUT_PATH -o ! -e $TEMPLATES_OUTPUT_PATH ]; then build_asset "$TEMPLATES_PACKAGE" "$TEMPLATES_OUTPUT_PATH" "$TEMPLATES_INPUT_DIRS" break; fi done exit 0 cadvisor-0.27.1/build/boilerplate/000077500000000000000000000000001315410276000170135ustar00rootroot00000000000000cadvisor-0.27.1/build/boilerplate/boilerplate.go.txt000066400000000000000000000011411315410276000224570ustar00rootroot00000000000000// Copyright YEAR 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. cadvisor-0.27.1/build/boilerplate/boilerplate.py000077500000000000000000000112541315410276000216750ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2016 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. from __future__ import print_function import argparse import glob import json import mmap import os import re import sys parser = argparse.ArgumentParser() parser.add_argument("filenames", help="list of files to check, all files if unspecified", nargs='*') args = parser.parse_args() rootdir = os.path.dirname(__file__) + "/../../" rootdir = os.path.abspath(rootdir) def get_refs(): refs = {} for path in glob.glob(os.path.join(rootdir, "build/boilerplate/boilerplate.*.txt")): extension = os.path.basename(path).split(".")[1] ref_file = open(path, 'r') ref = ref_file.read().splitlines() ref_file.close() refs[extension] = ref return refs def file_passes(filename, refs, regexs): try: f = open(filename, 'r') except: return False data = f.read() f.close() extension = file_extension(filename) ref = refs[extension] # remove build tags from the top of Go files if extension == "go": p = regexs["go_build_constraints"] (data, found) = p.subn("", data, 1) # remove shebang from the top of shell files if extension == "sh": p = regexs["shebang"] (data, found) = p.subn("", data, 1) data = data.splitlines() # if our test file is smaller than the reference it surely fails! if len(ref) > len(data): return False # trim our file to the same number of lines as the reference file data = data[:len(ref)] p = regexs["year"] for d in data: if p.search(d): return False # Replace all occurrences of the regex "2016|2015|2014" with "YEAR" p = regexs["date"] for i, d in enumerate(data): (data[i], found) = p.subn('YEAR', d) if found != 0: break # if we don't match the reference at this point, fail if ref != data: return False return True def file_extension(filename): return os.path.splitext(filename)[1].split(".")[-1].lower() skipped_dirs = ['Godeps', 'vendor', 'third_party', '_gopath', '_output', '.git'] def normalize_files(files): newfiles = [] for pathname in files: if any(x in pathname for x in skipped_dirs): continue newfiles.append(pathname) for i, pathname in enumerate(newfiles): if not os.path.isabs(pathname): newfiles[i] = os.path.join(rootdir, pathname) return newfiles def get_files(extensions): files = [] if len(args.filenames) > 0: files = args.filenames else: for root, dirs, walkfiles in os.walk(rootdir): # don't visit certain dirs. This is just a performance improvement # as we would prune these later in normalize_files(). But doing it # cuts down the amount of filesystem walking we do and cuts down # the size of the file list for d in skipped_dirs: if d in dirs: dirs.remove(d) for name in walkfiles: pathname = os.path.join(root, name) files.append(pathname) files = normalize_files(files) outfiles = [] for pathname in files: extension = file_extension(pathname) if extension in extensions: outfiles.append(pathname) return outfiles def get_regexs(): regexs = {} # Search for "YEAR" which exists in the boilerplate, but shouldn't in the real thing regexs["year"] = re.compile( 'YEAR' ) # dates can be 2014, 2015 or 2016, company holder names can be anything regexs["date"] = re.compile( '(2014|2015|2016|2017|2018|2019|2020)' ) # strip // +build \n\n build constraints regexs["go_build_constraints"] = re.compile(r"^(// \+build.*\n)+\n", re.MULTILINE) # strip #!.* from shell scripts regexs["shebang"] = re.compile(r"^(#!.*\n)\n*", re.MULTILINE) return regexs def main(): regexs = get_regexs() refs = get_refs() filenames = get_files(refs.keys()) for filename in filenames: if not file_passes(filename, refs, regexs): print(filename, file=sys.stdout) if __name__ == "__main__": sys.exit(main()) cadvisor-0.27.1/build/boilerplate/boilerplate.py.txt000066400000000000000000000011531315410276000225050ustar00rootroot00000000000000#!/usr/bin/env python # Copyright YEAR 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. cadvisor-0.27.1/build/boilerplate/boilerplate.sh.txt000066400000000000000000000011241315410276000224650ustar00rootroot00000000000000# Copyright YEAR 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. cadvisor-0.27.1/build/build.sh000077500000000000000000000036071315410276000161550ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -e GO_FLAGS=${GO_FLAGS:-"-tags netgo"} # Extra go flags to use in the build. GO_CMD=${GO_CMD:-"install"} BUILD_USER=${BUILD_USER:-"${USER}@${HOSTNAME}"} BUILD_DATE=${BUILD_DATE:-$( date +%Y%m%d-%H:%M:%S )} VERBOSE=${VERBOSE:-} repo_path="github.com/google/cadvisor" version=$( git describe --tags --dirty --abbrev=14 | sed -E 's/-([0-9]+)-g/.\1+/' ) revision=$( git rev-parse --short HEAD 2> /dev/null || echo 'unknown' ) branch=$( git rev-parse --abbrev-ref HEAD 2> /dev/null || echo 'unknown' ) go_version=$( go version | sed -e 's/^[^0-9.]*\([0-9.]*\).*/\1/' ) # go 1.4 requires ldflags format to be "-X key value", not "-X key=value" ldseparator="=" if [ "${go_version:0:3}" = "1.4" ]; then ldseparator=" " fi ldflags=" -extldflags '-static' -X ${repo_path}/version.Version${ldseparator}${version} -X ${repo_path}/version.Revision${ldseparator}${revision} -X ${repo_path}/version.Branch${ldseparator}${branch} -X ${repo_path}/version.BuildUser${ldseparator}${BUILD_USER} -X ${repo_path}/version.BuildDate${ldseparator}${BUILD_DATE} -X ${repo_path}/version.GoVersion${ldseparator}${go_version}" echo ">> building cadvisor" if [ -n "$VERBOSE" ]; then echo "Building with -ldflags $ldflags" fi GOBIN=$PWD go "$GO_CMD" ${GO_FLAGS} -ldflags "${ldflags}" "${repo_path}" exit 0 cadvisor-0.27.1/build/check_boilerplate.sh000077500000000000000000000017101315410276000205060ustar00rootroot00000000000000#!/bin/bash # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail GIT_ROOT=$(dirname "${BASH_SOURCE}")/.. boiler="${GIT_ROOT}/build/boilerplate/boilerplate.py" files_need_boilerplate=($(${boiler} "$@")) if [[ ${#files_need_boilerplate[@]} -gt 0 ]]; then for file in "${files_need_boilerplate[@]}"; do echo "Boilerplate header is wrong for: ${file}" done exit 1 fi cadvisor-0.27.1/build/check_errorf.sh000077500000000000000000000015441315410276000175100ustar00rootroot00000000000000#!/bin/bash # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. GO_FILES=$(find . -not -wholename "*Godeps*" -not -wholename "*vendor*" -name "*.go") for FILE in ${GO_FILES}; do ERRS=`grep 'fmt.Errorf("[A-Z]' ${FILE}` if [ $? -eq 0 ] then echo Incorrect error format in file ${FILE}: $ERRS exit 1 fi done exit 0 cadvisor-0.27.1/build/check_gofmt.sh000077500000000000000000000017071315410276000173260ustar00rootroot00000000000000#!/bin/bash # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Check formatting on non Godep'd code. GOFMT_PATHS=$(find . -not -wholename "*.git*" -not -wholename "*Godeps*" -not -wholename "*vendor*" -not -name "." -type d) # Find any files with gofmt problems BAD_FILES=$(gofmt -s -l $GOFMT_PATHS) if [ -n "$BAD_FILES" ]; then echo "The following files are not properly formatted:" echo $BAD_FILES exit 1 fi cadvisor-0.27.1/build/integration.sh000077500000000000000000000034221315410276000173740ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2016 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. if [[ -n "${JENKINS_HOME}" ]]; then exec ./build/jenkins_e2e.sh fi set -e # Build the test binary. GO_FLAGS="-race" ./build/build.sh TEST_PID=$$ sudo printf "" # Refresh sudo credentials if necessary. function start { set +e # We want to handle errors if cAdvisor crashes. echo ">> starting cAdvisor locally" GORACE="halt_on_error=1" sudo -E ./cadvisor --docker_env_metadata_whitelist=TEST_VAR if [ $? != 0 ]; then echo "!! cAdvisor exited unexpectedly with Exit $?" kill $TEST_PID # cAdvisor crashed: abort testing. fi } start & RUNNER_PID=$! function cleanup { if pgrep cadvisor > /dev/null; then echo ">> stopping cAdvisor" sudo pkill -SIGINT cadvisor wait $RUNNER_PID fi } trap cleanup EXIT readonly TIMEOUT=30 # Timeout to wait for cAdvisor, in seconds. START=$(date +%s) while [ "$(curl -Gs http://localhost:8080/healthz)" != "ok" ]; do if (( $(date +%s) - $START > $TIMEOUT )); then echo "Timed out waiting for cAdvisor to start" exit 1 fi echo "Waiting for cAdvisor to start ..." sleep 1 done echo ">> running integration tests against local cAdvisor" go test github.com/google/cadvisor/integration/tests/... --vmodule=*=2 cadvisor-0.27.1/build/jenkins_e2e.sh000077500000000000000000000042521315410276000172470ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2016 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. set -e set -x BUILDER=${BUILDER:-false} # Whether this is running a PR builder job. export GO_FLAGS="-race" export GORACE="halt_on_error=1" # Check whether assets need to be rebuilt. FORCE=true build/assets.sh if [[ ! -z "$(git diff --name-only pages)" ]]; then echo "Found changes to UI assets:" git diff --name-only pages echo "Run: `make assets FORCE=true`" exit 1 fi # Build & test with go 1.7 docker run --rm \ -w "/go/src/github.com/google/cadvisor" \ -v "${GOPATH}/src/github.com/google/cadvisor:/go/src/github.com/google/cadvisor" \ golang:1.8 make all test-runner # Nodes that are currently stable. When tests fail on a specific node, and the failure is not remedied within a week, that node will be removed from this list. golden_nodes=( e2e-cadvisor-ubuntu-trusty e2e-cadvisor-container-vm-v20151215 e2e-cadvisor-container-vm-v20160127 e2e-cadvisor-rhel-7 ) # TODO: Add test on GCI # TODO: Add test for kubernetes default image # e2e-cadvisor-container-vm-v20160321 # TODO: Temporarily disabled for #1344 # e2e-cadvisor-coreos-beta # TODO: enable when docker 1.10 is working # e2e-cadvisor-ubuntu-trusty-docker110 # TODO: Always fails with "Network tx and rx bytes should not be equal" # e2e-cadvisor-centos-v7 max_retries=8 ./runner --logtostderr --test-retry-count=$max_retries \ --test-retry-whitelist=integration/runner/retrywhitelist.txt \ --ssh-options "-i /var/lib/jenkins/gce_keys/google_compute_engine -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o CheckHostIP=no -o StrictHostKeyChecking=no" \ ${golden_nodes[*]} cadvisor-0.27.1/build/release.sh000077500000000000000000000033221315410276000164700ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -e VERSION=$( git describe --tags --dirty --abbrev=14 | sed -E 's/-([0-9]+)-g/.\1+/' ) # Only allow releases of tagged versions. TAGGED='^v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta)[0-9]*)?$' if [[ ! "$VERSION" =~ $TAGGED ]]; then echo "Error: Only tagged versions are allowed for releases" >&2 echo "Found: $VERSION" >&2 exit 1 fi # Don't include hostname with release builds if ! git_user="$(git config --get user.email)"; then echo "Error: git user not set, use:" echo "git config user.email " exit 1 fi # Build the release. export BUILD_USER="$git_user" export BUILD_DATE=$( date +%Y%m%d ) # Release date is only to day-granularity export GO_CMD="build" # Don't use cached build objects for releases. export VERBOSE=true build/build.sh # Build the docker image echo ">> building cadvisor docker image" docker_tag="google/cadvisor:$VERSION" gcr_tag="gcr.io/google_containers/cadvisor:$VERSION" docker build -t $docker_tag -t $gcr_tag -f deploy/Dockerfile . echo echo "Release info:" echo "VERSION=$VERSION" sha256sum --tag cadvisor echo "docker image: $docker_tag" echo "gcr.io image: $gcr_tag" exit 0 cadvisor-0.27.1/cache/000077500000000000000000000000001315410276000144555ustar00rootroot00000000000000cadvisor-0.27.1/cache/cache.go000066400000000000000000000030021315410276000160420ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cache import info "github.com/google/cadvisor/info/v1" type Cache interface { // Add a ContainerStats for the specified container. AddStats(ref info.ContainerReference, stats *info.ContainerStats) error // Remove all cached information for the specified container. RemoveContainer(containerName string) error // Read most recent stats. numStats indicates max number of stats // returned. The returned stats must be consecutive observed stats. If // numStats < 0, then return all stats stored in the storage. The // returned stats should be sorted in time increasing order, i.e. Most // recent stats should be the last. RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) // Close will clear the state of the storage driver. The elements // stored in the underlying storage may or may not be deleted depending // on the implementation of the storage driver. Close() error } cadvisor-0.27.1/cache/memory/000077500000000000000000000000001315410276000157655ustar00rootroot00000000000000cadvisor-0.27.1/cache/memory/memory.go000066400000000000000000000073421315410276000176320ustar00rootroot00000000000000// 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 ( "fmt" "sync" "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/storage" "github.com/google/cadvisor/utils" "github.com/golang/glog" ) // TODO(vmarmol): See about refactoring this class, we have an unecessary redirection of containerCache and InMemoryCache. // containerCache is used to store per-container information type containerCache struct { ref info.ContainerReference recentStats *utils.TimedStore maxAge time.Duration lock sync.RWMutex } func (self *containerCache) AddStats(stats *info.ContainerStats) error { self.lock.Lock() defer self.lock.Unlock() // Add the stat to storage. self.recentStats.Add(stats.Timestamp, stats) return nil } func (self *containerCache) RecentStats(start, end time.Time, maxStats int) ([]*info.ContainerStats, error) { self.lock.RLock() defer self.lock.RUnlock() result := self.recentStats.InTimeRange(start, end, maxStats) converted := make([]*info.ContainerStats, len(result)) for i, el := range result { converted[i] = el.(*info.ContainerStats) } return converted, nil } func newContainerStore(ref info.ContainerReference, maxAge time.Duration) *containerCache { return &containerCache{ ref: ref, recentStats: utils.NewTimedStore(maxAge, -1), maxAge: maxAge, } } type InMemoryCache struct { lock sync.RWMutex containerCacheMap map[string]*containerCache maxAge time.Duration backend storage.StorageDriver } func (self *InMemoryCache) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { var cstore *containerCache var ok bool func() { self.lock.Lock() defer self.lock.Unlock() if cstore, ok = self.containerCacheMap[ref.Name]; !ok { cstore = newContainerStore(ref, self.maxAge) self.containerCacheMap[ref.Name] = cstore } }() if self.backend != nil { // TODO(monnand): To deal with long delay write operations, we // may want to start a pool of goroutines to do write // operations. if err := self.backend.AddStats(ref, stats); err != nil { glog.Error(err) } } return cstore.AddStats(stats) } func (self *InMemoryCache) RecentStats(name string, start, end time.Time, maxStats int) ([]*info.ContainerStats, error) { var cstore *containerCache var ok bool err := func() error { self.lock.RLock() defer self.lock.RUnlock() if cstore, ok = self.containerCacheMap[name]; !ok { return fmt.Errorf("unable to find data for container %v", name) } return nil }() if err != nil { return nil, err } return cstore.RecentStats(start, end, maxStats) } func (self *InMemoryCache) Close() error { self.lock.Lock() self.containerCacheMap = make(map[string]*containerCache, 32) self.lock.Unlock() return nil } func (self *InMemoryCache) RemoveContainer(containerName string) error { self.lock.Lock() delete(self.containerCacheMap, containerName) self.lock.Unlock() return nil } func New( maxAge time.Duration, backend storage.StorageDriver, ) *InMemoryCache { ret := &InMemoryCache{ containerCacheMap: make(map[string]*containerCache, 32), maxAge: maxAge, backend: backend, } return ret } cadvisor-0.27.1/cache/memory/memory_test.go000066400000000000000000000052541315410276000206710ustar00rootroot00000000000000// 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" "time" info "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const containerName = "/container" var ( containerRef = info.ContainerReference{Name: containerName} zero time.Time ) // Make stats with the specified identifier. func makeStat(i int) *info.ContainerStats { return &info.ContainerStats{ Timestamp: zero.Add(time.Duration(i) * time.Second), Cpu: info.CpuStats{ LoadAverage: int32(i), }, } } func getRecentStats(t *testing.T, memoryCache *InMemoryCache, numStats int) []*info.ContainerStats { stats, err := memoryCache.RecentStats(containerName, zero, zero, numStats) require.Nil(t, err) return stats } func TestAddStats(t *testing.T) { memoryCache := New(60*time.Second, nil) assert := assert.New(t) assert.Nil(memoryCache.AddStats(containerRef, makeStat(0))) assert.Nil(memoryCache.AddStats(containerRef, makeStat(1))) assert.Nil(memoryCache.AddStats(containerRef, makeStat(2))) assert.Nil(memoryCache.AddStats(containerRef, makeStat(0))) containerRef2 := info.ContainerReference{ Name: "/container2", } assert.Nil(memoryCache.AddStats(containerRef2, makeStat(0))) assert.Nil(memoryCache.AddStats(containerRef2, makeStat(1))) } func TestRecentStatsNoRecentStats(t *testing.T) { memoryCache := makeWithStats(0) _, err := memoryCache.RecentStats(containerName, zero, zero, 60) assert.NotNil(t, err) } // Make an instance of InMemoryCache with n stats. func makeWithStats(n int) *InMemoryCache { memoryCache := New(60*time.Second, nil) for i := 0; i < n; i++ { memoryCache.AddStats(containerRef, makeStat(i)) } return memoryCache } func TestRecentStatsGetZeroStats(t *testing.T) { memoryCache := makeWithStats(10) assert.Len(t, getRecentStats(t, memoryCache, 0), 0) } func TestRecentStatsGetSomeStats(t *testing.T) { memoryCache := makeWithStats(10) assert.Len(t, getRecentStats(t, memoryCache, 5), 5) } func TestRecentStatsGetAllStats(t *testing.T) { memoryCache := makeWithStats(10) assert.Len(t, getRecentStats(t, memoryCache, -1), 10) } cadvisor-0.27.1/cadvisor.go000066400000000000000000000160451315410276000155610ustar00rootroot00000000000000// 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" "net/http" "net/http/pprof" "os" "os/signal" "runtime" "strings" "syscall" "time" "github.com/google/cadvisor/container" cadvisorhttp "github.com/google/cadvisor/http" "github.com/google/cadvisor/manager" "github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/version" "crypto/tls" "github.com/golang/glog" ) var argIp = flag.String("listen_ip", "", "IP to listen on, defaults to all IPs") var argPort = flag.Int("port", 8080, "port to listen") var maxProcs = flag.Int("max_procs", 0, "max number of CPUs that can be used simultaneously. Less than 1 for default (number of cores).") var versionFlag = flag.Bool("version", false, "print cAdvisor version and exit") var httpAuthFile = flag.String("http_auth_file", "", "HTTP auth file for the web UI") var httpAuthRealm = flag.String("http_auth_realm", "localhost", "HTTP auth realm for the web UI") var httpDigestFile = flag.String("http_digest_file", "", "HTTP digest file for the web UI") var httpDigestRealm = flag.String("http_digest_realm", "localhost", "HTTP digest file for the web UI") var prometheusEndpoint = flag.String("prometheus_endpoint", "/metrics", "Endpoint to expose Prometheus metrics on") var maxHousekeepingInterval = flag.Duration("max_housekeeping_interval", 60*time.Second, "Largest interval to allow between container housekeepings") var allowDynamicHousekeeping = flag.Bool("allow_dynamic_housekeeping", true, "Whether to allow the housekeeping interval to be dynamic") var enableProfiling = flag.Bool("profiling", false, "Enable profiling via web interface host:port/debug/pprof/") var collectorCert = flag.String("collector_cert", "", "Collector's certificate, exposed to endpoints for certificate based authentication.") var collectorKey = flag.String("collector_key", "", "Key for the collector's certificate") var ( // Metrics to be ignored. // Tcp metrics are ignored by default. ignoreMetrics metricSetValue = metricSetValue{container.MetricSet{ container.NetworkTcpUsageMetrics: struct{}{}, container.NetworkUdpUsageMetrics: struct{}{}, }} // List of metrics that can be ignored. ignoreWhitelist = container.MetricSet{ container.DiskUsageMetrics: struct{}{}, container.NetworkUsageMetrics: struct{}{}, container.NetworkTcpUsageMetrics: struct{}{}, container.NetworkUdpUsageMetrics: struct{}{}, } ) type metricSetValue struct { container.MetricSet } func (ml *metricSetValue) String() string { var values []string for metric, _ := range ml.MetricSet { values = append(values, string(metric)) } return strings.Join(values, ",") } func (ml *metricSetValue) Set(value string) error { ml.MetricSet = container.MetricSet{} if value == "" { return nil } for _, metric := range strings.Split(value, ",") { if ignoreWhitelist.Has(container.MetricKind(metric)) { (*ml).Add(container.MetricKind(metric)) } else { return fmt.Errorf("unsupported metric %q specified in disable_metrics", metric) } } return nil } func init() { flag.Var(&ignoreMetrics, "disable_metrics", "comma-separated list of `metrics` to be disabled. Options are 'disk', 'network', 'tcp', 'udp'. Note: tcp and udp are disabled by default due to high CPU usage.") } func main() { defer glog.Flush() flag.Parse() if *versionFlag { fmt.Printf("cAdvisor version %s (%s)\n", version.Info["version"], version.Info["revision"]) os.Exit(0) } setMaxProcs() memoryStorage, err := NewMemoryStorage() if err != nil { glog.Fatalf("Failed to initialize storage driver: %s", err) } sysFs := sysfs.NewRealSysFs() collectorHttpClient := createCollectorHttpClient(*collectorCert, *collectorKey) containerManager, err := manager.New(memoryStorage, sysFs, *maxHousekeepingInterval, *allowDynamicHousekeeping, ignoreMetrics.MetricSet, &collectorHttpClient) if err != nil { glog.Fatalf("Failed to create a Container Manager: %s", err) } mux := http.NewServeMux() if *enableProfiling { mux.HandleFunc("/debug/pprof/", pprof.Index) mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) } // Register all HTTP handlers. err = cadvisorhttp.RegisterHandlers(mux, containerManager, *httpAuthFile, *httpAuthRealm, *httpDigestFile, *httpDigestRealm) if err != nil { glog.Fatalf("Failed to register HTTP handlers: %v", err) } cadvisorhttp.RegisterPrometheusHandler(mux, containerManager, *prometheusEndpoint, nil) // Start the manager. if err := containerManager.Start(); err != nil { glog.Fatalf("Failed to start container manager: %v", err) } // Install signal handler. installSignalHandler(containerManager) glog.Infof("Starting cAdvisor version: %s-%s on port %d", version.Info["version"], version.Info["revision"], *argPort) addr := fmt.Sprintf("%s:%d", *argIp, *argPort) glog.Fatal(http.ListenAndServe(addr, mux)) } func setMaxProcs() { // TODO(vmarmol): Consider limiting if we have a CPU mask in effect. // Allow as many threads as we have cores unless the user specified a value. var numProcs int if *maxProcs < 1 { numProcs = runtime.NumCPU() } else { numProcs = *maxProcs } runtime.GOMAXPROCS(numProcs) // Check if the setting was successful. actualNumProcs := runtime.GOMAXPROCS(0) if actualNumProcs != numProcs { glog.Warningf("Specified max procs of %v but using %v", numProcs, actualNumProcs) } } func installSignalHandler(containerManager manager.Manager) { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM) // Block until a signal is received. go func() { sig := <-c if err := containerManager.Stop(); err != nil { glog.Errorf("Failed to stop container manager: %v", err) } glog.Infof("Exiting given signal: %v", sig) os.Exit(0) }() } func createCollectorHttpClient(collectorCert, collectorKey string) http.Client { //Enable accessing insecure endpoints. We should be able to access metrics from any endpoint tlsConfig := &tls.Config{ InsecureSkipVerify: true, } if collectorCert != "" { if collectorKey == "" { glog.Fatal("The collector_key value must be specified if the collector_cert value is set.") } cert, err := tls.LoadX509KeyPair(collectorCert, collectorKey) if err != nil { glog.Fatalf("Failed to use the collector certificate and key: %s", err) } tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.BuildNameToCertificate() } transport := &http.Transport{ TLSClientConfig: tlsConfig, } return http.Client{Transport: transport} } cadvisor-0.27.1/cadvisor_test.go000066400000000000000000000033741315410276000166210ustar00rootroot00000000000000// 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" "testing" "github.com/google/cadvisor/container" "github.com/stretchr/testify/assert" ) func TestTcpMetricsAreDisabledByDefault(t *testing.T) { assert.True(t, ignoreMetrics.Has(container.NetworkTcpUsageMetrics)) flag.Parse() assert.True(t, ignoreMetrics.Has(container.NetworkTcpUsageMetrics)) } func TestUdpMetricsAreDisabledByDefault(t *testing.T) { assert.True(t, ignoreMetrics.Has(container.NetworkUdpUsageMetrics)) flag.Parse() assert.True(t, ignoreMetrics.Has(container.NetworkUdpUsageMetrics)) } func TestIgnoreMetrics(t *testing.T) { tests := []struct { value string expected []container.MetricKind }{ {"", []container.MetricKind{}}, {"disk", []container.MetricKind{container.DiskUsageMetrics}}, {"disk,tcp,network", []container.MetricKind{container.DiskUsageMetrics, container.NetworkTcpUsageMetrics, container.NetworkUsageMetrics}}, } for _, test := range tests { assert.NoError(t, ignoreMetrics.Set(test.value)) assert.Equal(t, len(test.expected), len(ignoreMetrics.MetricSet)) for _, expected := range test.expected { assert.True(t, ignoreMetrics.Has(expected), "Missing %s", expected) } } } cadvisor-0.27.1/client/000077500000000000000000000000001315410276000146705ustar00rootroot00000000000000cadvisor-0.27.1/client/README.md000066400000000000000000000034171315410276000161540ustar00rootroot00000000000000# Example REST API Client This is an implementation of a cAdvisor REST API in Go. You can use it like this: ```go client, err := client.NewClient("http://192.168.59.103:8080/") ``` Obviously, replace the URL with the path to your actual cAdvisor REST endpoint. ### MachineInfo ```go client.MachineInfo() ``` This method returns a cadvisor/v1.MachineInfo struct with all the fields filled in. Here is an example return value: ``` (*v1.MachineInfo)(0xc208022b10)({ NumCores: (int) 4, MemoryCapacity: (int64) 2106028032, Filesystems: ([]v1.FsInfo) (len=1 cap=4) { (v1.FsInfo) { Device: (string) (len=9) "/dev/sda1", Capacity: (uint64) 19507089408 } } }) ``` You can see the full specification of the [MachineInfo struct in the source](../info/v1/machine.go#L131) ### ContainerInfo Given a container name and a [ContainerInfoRequest](../info/v1/container.go#L101), will return all information about the specified container. See the [ContainerInfoRequest struct in the source](../info/v1/container.go#L101) for the full specification. ```go request := v1.ContainerInfoRequest{NumStats: 10} sInfo, err := client.ContainerInfo("/docker/d9d3eb10179e6f93a...", &request) ``` Returns a [ContainerInfo struct](../info/v1/container.go#L128) ### SubcontainersInfo Given a container name and a [ContainerInfoRequest](../info/v1/container.go#L101), will recursively return all info about the container and all subcontainers contained within the container. See the [ContainerInfoRequest struct in the source](../info/v1/container.go#L101) for the full specification. ```go request := v1.ContainerInfoRequest{NumStats: 10} sInfo, err := client.SubcontainersInfo("/docker", &request) ``` Returns a [ContainerInfo struct](../info/v1/container.go#L128) with the Subcontainers field populated. cadvisor-0.27.1/client/client.go000066400000000000000000000157521315410276000165070ustar00rootroot00000000000000// 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. // This is an implementation of a cAdvisor REST API in Go. // To use it, create a client (replace the URL with your actual cAdvisor REST endpoint): // client, err := client.NewClient("http://192.168.59.103:8080/") // Then, the client interface exposes go methods corresponding to the REST endpoints. package client import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "path" "strings" "github.com/google/cadvisor/info/v1" "github.com/golang/glog" "time" ) // Client represents the base URL for a cAdvisor client. type Client struct { baseUrl string httpClient *http.Client } // NewClient returns a new v1.3 client with the specified base URL. func NewClient(url string) (*Client, error) { return newClient(url, http.DefaultClient) } // NewClientWithTimeout returns a new v1.3 client with the specified base URL and http client timeout. func NewClientWithTimeout(url string, timeout time.Duration) (*Client, error) { return newClient(url, &http.Client{ Timeout: timeout, }) } func newClient(url string, client *http.Client) (*Client, error) { if !strings.HasSuffix(url, "/") { url += "/" } return &Client{ baseUrl: fmt.Sprintf("%sapi/v1.3/", url), httpClient: client, }, nil } // Returns all past events that satisfy the request func (self *Client) EventStaticInfo(name string) (einfo []*v1.Event, err error) { u := self.eventsInfoUrl(name) ret := new([]*v1.Event) if err = self.httpGetJsonData(ret, nil, u, "event info"); err != nil { return } einfo = *ret return } // Streams all events that occur that satisfy the request into the channel // that is passed func (self *Client) EventStreamingInfo(name string, einfo chan *v1.Event) (err error) { u := self.eventsInfoUrl(name) if err = self.getEventStreamingData(u, einfo); err != nil { return } return nil } // MachineInfo returns the JSON machine information for this client. // A non-nil error result indicates a problem with obtaining // the JSON machine information data. func (self *Client) MachineInfo() (minfo *v1.MachineInfo, err error) { u := self.machineInfoUrl() ret := new(v1.MachineInfo) if err = self.httpGetJsonData(ret, nil, u, "machine info"); err != nil { return } minfo = ret return } // ContainerInfo returns the JSON container information for the specified // container and request. func (self *Client) ContainerInfo(name string, query *v1.ContainerInfoRequest) (cinfo *v1.ContainerInfo, err error) { u := self.containerInfoUrl(name) ret := new(v1.ContainerInfo) if err = self.httpGetJsonData(ret, query, u, fmt.Sprintf("container info for %q", name)); err != nil { return } cinfo = ret return } // Returns the information about all subcontainers (recursive) of the specified container (including itself). func (self *Client) SubcontainersInfo(name string, query *v1.ContainerInfoRequest) ([]v1.ContainerInfo, error) { var response []v1.ContainerInfo url := self.subcontainersInfoUrl(name) err := self.httpGetJsonData(&response, query, url, fmt.Sprintf("subcontainers container info for %q", name)) if err != nil { return []v1.ContainerInfo{}, err } return response, nil } // Returns the JSON container information for the specified // Docker container and request. func (self *Client) DockerContainer(name string, query *v1.ContainerInfoRequest) (cinfo v1.ContainerInfo, err error) { u := self.dockerInfoUrl(name) ret := make(map[string]v1.ContainerInfo) if err = self.httpGetJsonData(&ret, query, u, fmt.Sprintf("Docker container info for %q", name)); err != nil { return } if len(ret) != 1 { err = fmt.Errorf("expected to only receive 1 Docker container: %+v", ret) return } for _, cont := range ret { cinfo = cont } return } // Returns the JSON container information for all Docker containers. func (self *Client) AllDockerContainers(query *v1.ContainerInfoRequest) (cinfo []v1.ContainerInfo, err error) { u := self.dockerInfoUrl("/") ret := make(map[string]v1.ContainerInfo) if err = self.httpGetJsonData(&ret, query, u, "all Docker containers info"); err != nil { return } cinfo = make([]v1.ContainerInfo, 0, len(ret)) for _, cont := range ret { cinfo = append(cinfo, cont) } return } func (self *Client) machineInfoUrl() string { return self.baseUrl + path.Join("machine") } func (self *Client) containerInfoUrl(name string) string { return self.baseUrl + path.Join("containers", name) } func (self *Client) subcontainersInfoUrl(name string) string { return self.baseUrl + path.Join("subcontainers", name) } func (self *Client) dockerInfoUrl(name string) string { return self.baseUrl + path.Join("docker", name) } func (self *Client) eventsInfoUrl(name string) string { return self.baseUrl + path.Join("events", name) } func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName string) error { var resp *http.Response var err error if postData != nil { data, marshalErr := json.Marshal(postData) if marshalErr != nil { return fmt.Errorf("unable to marshal data: %v", marshalErr) } resp, err = self.httpClient.Post(url, "application/json", bytes.NewBuffer(data)) } else { resp, err = self.httpClient.Get(url) } if err != nil { return fmt.Errorf("unable to get %q from %q: %v", infoName, url, err) } if resp == nil { return fmt.Errorf("received empty response for %q from %q", infoName, url) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { err = fmt.Errorf("unable to read all %q from %q: %v", infoName, url, err) return err } if resp.StatusCode != 200 { return fmt.Errorf("request %q failed with error: %q", url, strings.TrimSpace(string(body))) } if err = json.Unmarshal(body, data); err != nil { err = fmt.Errorf("unable to unmarshal %q (Body: %q) from %q with error: %v", infoName, string(body), url, err) return err } return nil } func (self *Client) getEventStreamingData(url string, einfo chan *v1.Event) error { req, err := http.NewRequest("GET", url, nil) if err != nil { return err } resp, err := self.httpClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("Status code is not OK: %v (%s)", resp.StatusCode, resp.Status) } dec := json.NewDecoder(resp.Body) var m *v1.Event = &v1.Event{} for { err := dec.Decode(m) if err != nil { if err == io.EOF { break } // if called without &stream=true will not be able to parse event and will trigger fatal glog.Fatalf("Received error %v", err) } einfo <- m } return nil } cadvisor-0.27.1/client/client_test.go000066400000000000000000000127321315410276000175410ustar00rootroot00000000000000// 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 client import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "path" "reflect" "strings" "testing" "time" info "github.com/google/cadvisor/info/v1" itest "github.com/google/cadvisor/info/v1/test" "github.com/kr/pretty" ) func testGetJsonData( expected interface{}, f func() (interface{}, error), ) error { reply, err := f() if err != nil { return fmt.Errorf("unable to retrieve data: %v", err) } if !reflect.DeepEqual(reply, expected) { return pretty.Errorf("retrieved wrong data: %# v != %# v", reply, expected) } return nil } func cadvisorTestClient(path string, expectedPostObj *info.ContainerInfoRequest, replyObj interface{}, t *testing.T) (*Client, *httptest.Server, error) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == path { if expectedPostObj != nil { expectedPostObjEmpty := new(info.ContainerInfoRequest) decoder := json.NewDecoder(r.Body) if err := decoder.Decode(expectedPostObjEmpty); err != nil { t.Errorf("Received invalid object: %v", err) } if expectedPostObj.NumStats != expectedPostObjEmpty.NumStats || expectedPostObj.Start.Unix() != expectedPostObjEmpty.Start.Unix() || expectedPostObj.End.Unix() != expectedPostObjEmpty.End.Unix() { t.Errorf("Received unexpected object: %+v, expected: %+v", expectedPostObjEmpty, expectedPostObj) } } encoder := json.NewEncoder(w) encoder.Encode(replyObj) } 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 } // TestGetMachineInfo performs one test to check if MachineInfo() // in a cAdvisor client returns the correct result. func TestGetMachineinfo(t *testing.T) { minfo := &info.MachineInfo{ NumCores: 8, MemoryCapacity: 31625871360, DiskMap: map[string]info.DiskInfo{ "8:0": { Name: "sda", Major: 8, Minor: 0, Size: 10737418240, }, }, } client, server, err := cadvisorTestClient("/api/v1.3/machine", nil, minfo, t) if err != nil { t.Fatalf("unable to get a client %v", err) } defer server.Close() returned, err := client.MachineInfo() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(returned, minfo) { t.Fatalf("received unexpected machine info") } } // TestGetContainerInfo generates a random container information object // and then checks that ContainerInfo returns the expected result. func TestGetContainerInfo(t *testing.T) { query := &info.ContainerInfoRequest{ NumStats: 3, } containerName := "/some/container" cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second) client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.3/containers%v", containerName), query, cinfo, t) if err != nil { t.Fatalf("unable to get a client %v", err) } defer server.Close() returned, err := client.ContainerInfo(containerName, query) if err != nil { t.Fatal(err) } if !returned.Eq(cinfo) { t.Error("received unexpected ContainerInfo") } } // Test a request failing func TestRequestFails(t *testing.T) { errorText := "there was an error" // Setup a server that simply fails. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, errorText, 500) })) client, err := NewClient(ts.URL) if err != nil { ts.Close() t.Fatal(err) } defer ts.Close() _, err = client.ContainerInfo("/", &info.ContainerInfoRequest{NumStats: 3}) if err == nil { t.Fatalf("Expected non-nil error") } expectedError := fmt.Sprintf("request failed with error: %q", errorText) if strings.Contains(err.Error(), expectedError) { t.Fatalf("Expected error %q but received %q", expectedError, err) } } func TestGetSubcontainersInfo(t *testing.T) { query := &info.ContainerInfoRequest{ NumStats: 3, } containerName := "/some/container" cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second) cinfo1 := itest.GenerateRandomContainerInfo(path.Join(containerName, "sub1"), 4, query, 1*time.Second) cinfo2 := itest.GenerateRandomContainerInfo(path.Join(containerName, "sub2"), 4, query, 1*time.Second) response := []info.ContainerInfo{ *cinfo, *cinfo1, *cinfo2, } client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.3/subcontainers%v", containerName), query, response, t) if err != nil { t.Fatalf("unable to get a client %v", err) } defer server.Close() returned, err := client.SubcontainersInfo(containerName, query) if err != nil { t.Fatal(err) } if len(returned) != 3 { t.Errorf("unexpected number of results: got %d, expected 3", len(returned)) } if !returned[0].Eq(cinfo) { t.Error("received unexpected ContainerInfo") } if !returned[1].Eq(cinfo1) { t.Error("received unexpected ContainerInfo") } if !returned[2].Eq(cinfo2) { t.Error("received unexpected ContainerInfo") } } cadvisor-0.27.1/client/clientexample/000077500000000000000000000000001315410276000175225ustar00rootroot00000000000000cadvisor-0.27.1/client/clientexample/main.go000066400000000000000000000034441315410276000210020ustar00rootroot00000000000000// 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" "github.com/google/cadvisor/client" info "github.com/google/cadvisor/info/v1" "github.com/golang/glog" ) func staticClientExample() { staticClient, err := client.NewClient("http://localhost:8080/") if err != nil { glog.Errorf("tried to make client and got error %v", err) return } einfo, err := staticClient.EventStaticInfo("?oom_events=true") if err != nil { glog.Errorf("got error retrieving event info: %v", err) return } for idx, ev := range einfo { glog.Infof("static einfo %v: %v", idx, ev) } } func streamingClientExample(url string) { streamingClient, err := client.NewClient("http://localhost:8080/") if err != nil { glog.Errorf("tried to make client and got error %v", err) return } einfo := make(chan *info.Event) go func() { err = streamingClient.EventStreamingInfo(url, einfo) if err != nil { glog.Errorf("got error retrieving event info: %v", err) return } }() for ev := range einfo { glog.Infof("streaming einfo: %v\n", ev) } } // demonstrates how to use event clients func main() { flag.Parse() staticClientExample() streamingClientExample("?creation_events=true&stream=true&oom_events=true&deletion_events=true") } cadvisor-0.27.1/client/v2/000077500000000000000000000000001315410276000152175ustar00rootroot00000000000000cadvisor-0.27.1/client/v2/README.md000066400000000000000000000033121315410276000164750ustar00rootroot00000000000000# Example REST API Client This is an implementation of a cAdvisor REST API in Go. You can use it like this: ```go client, err := client.NewClient("http://192.168.59.103:8080/") ``` Obviously, replace the URL with the path to your actual cAdvisor REST endpoint. ### MachineInfo ```go client.MachineInfo() ``` There is no v2 MachineInfo API, so the v2 client exposes the [v1 MachineInfo](../../info/v1/machine.go#L131) ``` (*v1.MachineInfo)(0xc208022b10)({ NumCores: (int) 4, MemoryCapacity: (int64) 2106028032, Filesystems: ([]v1.FsInfo) (len=1 cap=4) { (v1.FsInfo) { Device: (string) (len=9) "/dev/sda1", Capacity: (uint64) 19507089408 } } }) ``` You can see the full specification of the [MachineInfo struct in the source](../../info/v1/machine.go#L131) ### VersionInfo ```go client.VersionInfo() ``` This method returns the cAdvisor version. ### Attributes ```go client.Attributes() ``` This method returns a [cadvisor/info/v2/Attributes](../../info/v2/machine.go#L24) struct with all the fields filled in. Attributes includes hardware attributes (as returned by MachineInfo) as well as software attributes (eg. software versions). Here is an example return value: ``` (*v2.Attributes)({ KernelVersion: (string) (len=17) "3.13.0-44-generic" ContainerOsVersion: (string) (len=18) "Ubuntu 14.04.1 LTS" DockerVersion: (string) (len=9) "1.5.0-rc4" CadvisorVersion: (string) (len=6) "0.10.1" NumCores: (int) 4, MemoryCapacity: (int64) 2106028032, Filesystems: ([]v2.FsInfo) (len=1 cap=4) { (v2.FsInfo) { Device: (string) (len=9) "/dev/sda1", Capacity: (uint64) 19507089408 } } }) ``` You can see the full specification of the [Attributes struct in the source](../../info/v2/machine.go#L24) cadvisor-0.27.1/client/v2/client.go000066400000000000000000000117321315410276000170300ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Client library to programmatically access cAdvisor API. package v2 import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "path" "strconv" "strings" v1 "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/info/v2" ) // Client represents the base URL for a cAdvisor client. type Client struct { baseUrl string } // NewClient returns a new client with the specified base URL. func NewClient(url string) (*Client, error) { if !strings.HasSuffix(url, "/") { url += "/" } return &Client{ baseUrl: fmt.Sprintf("%sapi/v2.1/", url), }, nil } // MachineInfo returns the JSON machine information for this client. // A non-nil error result indicates a problem with obtaining // the JSON machine information data. func (self *Client) MachineInfo() (minfo *v1.MachineInfo, err error) { u := self.machineInfoUrl() ret := new(v1.MachineInfo) if err = self.httpGetJsonData(ret, nil, u, "machine info"); err != nil { return } minfo = ret return } // MachineStats returns the JSON machine statistics for this client. // A non-nil error result indicates a problem with obtaining // the JSON machine information data. func (self *Client) MachineStats() ([]v2.MachineStats, error) { var ret []v2.MachineStats u := self.machineStatsUrl() err := self.httpGetJsonData(&ret, nil, u, "machine stats") return ret, err } // VersionInfo returns the version info for cAdvisor. func (self *Client) VersionInfo() (version string, err error) { u := self.versionInfoUrl() version, err = self.httpGetString(u, "version info") return } // Attributes returns hardware and software attributes of the machine. func (self *Client) Attributes() (attr *v2.Attributes, err error) { u := self.attributesUrl() ret := new(v2.Attributes) if err = self.httpGetJsonData(ret, nil, u, "attributes"); err != nil { return } attr = ret return } // Stats returns stats for the requested container. func (self *Client) Stats(name string, request *v2.RequestOptions) (map[string]v2.ContainerInfo, error) { u := self.statsUrl(name) ret := make(map[string]v2.ContainerInfo) data := url.Values{ "type": []string{request.IdType}, "count": []string{strconv.Itoa(request.Count)}, "recursive": []string{strconv.FormatBool(request.Recursive)}, } u = fmt.Sprintf("%s?%s", u, data.Encode()) if err := self.httpGetJsonData(&ret, nil, u, "stats"); err != nil { return nil, err } return ret, nil } func (self *Client) machineInfoUrl() string { return self.baseUrl + path.Join("machine") } func (self *Client) machineStatsUrl() string { return self.baseUrl + path.Join("machinestats") } func (self *Client) versionInfoUrl() string { return self.baseUrl + path.Join("version") } func (self *Client) attributesUrl() string { return self.baseUrl + path.Join("attributes") } func (self *Client) statsUrl(name string) string { return self.baseUrl + path.Join("stats", name) } func (self *Client) httpGetResponse(postData interface{}, urlPath, infoName string) ([]byte, error) { var resp *http.Response var err error if postData != nil { data, marshalErr := json.Marshal(postData) if marshalErr != nil { return nil, fmt.Errorf("unable to marshal data: %v", marshalErr) } resp, err = http.Post(urlPath, "application/json", bytes.NewBuffer(data)) } else { resp, err = http.Get(urlPath) } if err != nil { return nil, fmt.Errorf("unable to post %q to %q: %v", infoName, urlPath, err) } if resp == nil { return nil, fmt.Errorf("received empty response for %q from %q", infoName, urlPath) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { err = fmt.Errorf("unable to read all %q from %q: %v", infoName, urlPath, err) return nil, err } if resp.StatusCode != 200 { return nil, fmt.Errorf("request %q failed with error: %q", urlPath, strings.TrimSpace(string(body))) } return body, nil } func (self *Client) httpGetString(url, infoName string) (string, error) { body, err := self.httpGetResponse(nil, url, infoName) if err != nil { return "", err } return string(body), nil } func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName string) error { body, err := self.httpGetResponse(postData, url, infoName) if err != nil { return err } if err = json.Unmarshal(body, data); err != nil { err = fmt.Errorf("unable to unmarshal %q (Body: %q) from %q with error: %v", infoName, string(body), url, err) return err } return nil } cadvisor-0.27.1/client/v2/client_test.go000066400000000000000000000136371315410276000200750ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v2 import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "reflect" "strings" "testing" "time" "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/info/v2" "github.com/stretchr/testify/assert" "github.com/kr/pretty" ) func testGetJsonData( expected interface{}, f func() (interface{}, error), ) error { reply, err := f() if err != nil { return fmt.Errorf("unable to retrieve data: %v", err) } if !reflect.DeepEqual(reply, expected) { return pretty.Errorf("retrieved wrong data: %# v != %# v", reply, expected) } return nil } func cadvisorTestClient(path string, expectedPostObj *v1.ContainerInfoRequest, replyObj interface{}, t *testing.T) (*Client, *httptest.Server, error) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == path { if expectedPostObj != nil { expectedPostObjEmpty := new(v1.ContainerInfoRequest) decoder := json.NewDecoder(r.Body) if err := decoder.Decode(expectedPostObjEmpty); err != nil { t.Errorf("Received invalid object: %v", err) } if expectedPostObj.NumStats != expectedPostObjEmpty.NumStats || expectedPostObj.Start.Unix() != expectedPostObjEmpty.Start.Unix() || expectedPostObj.End.Unix() != expectedPostObjEmpty.End.Unix() { t.Errorf("Received unexpected object: %+v, expected: %+v", expectedPostObjEmpty, expectedPostObj) } } encoder := json.NewEncoder(w) encoder.Encode(replyObj) } else if r.URL.Path == "/api/v2.1/version" { fmt.Fprintf(w, "0.1.2") } 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 } // TestGetMachineInfo performs one test to check if MachineInfo() // in a cAdvisor client returns the correct result. func TestGetMachineInfo(t *testing.T) { mv1 := &v1.MachineInfo{ NumCores: 8, MemoryCapacity: 31625871360, DiskMap: map[string]v1.DiskInfo{ "8:0": { Name: "sda", Major: 8, Minor: 0, Size: 10737418240, }, }, } client, server, err := cadvisorTestClient("/api/v2.1/machine", nil, mv1, t) if err != nil { t.Fatalf("unable to get a client %v", err) } defer server.Close() returned, err := client.MachineInfo() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(returned, mv1) { t.Fatalf("received unexpected machine v1") } } // TestGetVersionV1 performs one test to check if VersionV1() // in a cAdvisor client returns the correct result. func TestGetVersionv1(t *testing.T) { version := "0.1.2" client, server, err := cadvisorTestClient("", nil, version, t) if err != nil { t.Fatalf("unable to get a client %v", err) } defer server.Close() returned, err := client.VersionInfo() if err != nil { t.Fatal(err) } if returned != version { t.Fatalf("received unexpected version v1") } } // TestAttributes performs one test to check if Attributes() // in a cAdvisor client returns the correct result. func TestGetAttributes(t *testing.T) { attr := &v2.Attributes{ KernelVersion: "3.3.0", ContainerOsVersion: "Ubuntu 14.4", DockerVersion: "Docker 1.5", CadvisorVersion: "0.1.2", NumCores: 8, MemoryCapacity: 31625871360, DiskMap: map[string]v1.DiskInfo{ "8:0": { Name: "sda", Major: 8, Minor: 0, Size: 10737418240, }, }, } client, server, err := cadvisorTestClient("/api/v2.1/attributes", nil, attr, t) if err != nil { t.Fatalf("unable to get a client %v", err) } defer server.Close() returned, err := client.Attributes() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(returned, attr) { t.Fatalf("received unexpected attributes") } } // TestMachineStats performs one test to check if MachineStats() // in a cAdvisor client returns the correct result. func TestMachineStats(t *testing.T) { machineStats := []v2.MachineStats{ { Timestamp: time.Now(), Cpu: &v1.CpuStats{ Usage: v1.CpuUsage{ Total: 100000, }, LoadAverage: 10, }, Filesystem: []v2.MachineFsStats{ { Device: "sda1", }, }, }, } client, server, err := cadvisorTestClient("/api/v2.1/machinestats", nil, &machineStats, t) if err != nil { t.Fatalf("unable to get a client %v", err) } defer server.Close() returned, err := client.MachineStats() if err != nil { t.Fatal(err) } assert.Len(t, returned, len(machineStats)) if !reflect.DeepEqual(returned[0].Cpu, machineStats[0].Cpu) { t.Fatalf("received unexpected machine stats\nExp: %+v\nAct: %+v", machineStats, returned) } if !reflect.DeepEqual(returned[0].Filesystem, machineStats[0].Filesystem) { t.Fatalf("received unexpected machine stats\nExp: %+v\nAct: %+v", machineStats, returned) } } func TestRequestFails(t *testing.T) { errorText := "there was an error" // Setup a server that simply fails. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, errorText, 500) })) client, err := NewClient(ts.URL) if err != nil { ts.Close() t.Fatal(err) } defer ts.Close() _, err = client.MachineInfo() if err == nil { t.Fatalf("Expected non-nil error") } expectedError := fmt.Sprintf("request failed with error: %q", errorText) if strings.Contains(err.Error(), expectedError) { t.Fatalf("Expected error %q but received %q", expectedError, err) } } cadvisor-0.27.1/collector/000077500000000000000000000000001315410276000154005ustar00rootroot00000000000000cadvisor-0.27.1/collector/collector_manager.go000066400000000000000000000055551315410276000214210ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package collector import ( "fmt" "strings" "time" "github.com/google/cadvisor/info/v1" ) const metricLabelPrefix = "io.cadvisor.metric." type GenericCollectorManager struct { Collectors []*collectorData NextCollectionTime time.Time } type collectorData struct { collector Collector nextCollectionTime time.Time } // Returns a new CollectorManager that is thread-compatible. func NewCollectorManager() (CollectorManager, error) { return &GenericCollectorManager{ Collectors: []*collectorData{}, NextCollectionTime: time.Now(), }, nil } func GetCollectorConfigs(labels map[string]string) map[string]string { configs := map[string]string{} for k, v := range labels { if strings.HasPrefix(k, metricLabelPrefix) { name := strings.TrimPrefix(k, metricLabelPrefix) configs[name] = v } } return configs } func (cm *GenericCollectorManager) RegisterCollector(collector Collector) error { cm.Collectors = append(cm.Collectors, &collectorData{ collector: collector, nextCollectionTime: time.Now(), }) return nil } func (cm *GenericCollectorManager) GetSpec() ([]v1.MetricSpec, error) { metricSpec := []v1.MetricSpec{} for _, c := range cm.Collectors { specs := c.collector.GetSpec() metricSpec = append(metricSpec, specs...) } return metricSpec, nil } func (cm *GenericCollectorManager) Collect() (time.Time, map[string][]v1.MetricVal, error) { var errors []error // Collect from all collectors that are ready. var next time.Time metrics := map[string][]v1.MetricVal{} for _, c := range cm.Collectors { if c.nextCollectionTime.Before(time.Now()) { var err error c.nextCollectionTime, metrics, err = c.collector.Collect(metrics) if err != nil { errors = append(errors, err) } } // Keep track of the next collector that will be ready. if next.IsZero() || next.After(c.nextCollectionTime) { next = c.nextCollectionTime } } cm.NextCollectionTime = next return next, metrics, compileErrors(errors) } // Make an error slice into a single error. func compileErrors(errors []error) error { if len(errors) == 0 { return nil } res := make([]string, len(errors)) for i := range errors { res[i] = fmt.Sprintf("Error %d: %v", i, errors[i].Error()) } return fmt.Errorf("%s", strings.Join(res, ",")) } cadvisor-0.27.1/collector/collector_manager_test.go000066400000000000000000000040221315410276000224440ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package collector import ( "testing" "time" "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/assert" ) type fakeCollector struct { nextCollectionTime time.Time err error collectedFrom int } func (fc *fakeCollector) Collect(metric map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) { fc.collectedFrom++ return fc.nextCollectionTime, metric, fc.err } func (fc *fakeCollector) Name() string { return "fake-collector" } func (fc *fakeCollector) GetSpec() []v1.MetricSpec { return []v1.MetricSpec{} } func TestCollect(t *testing.T) { cm := &GenericCollectorManager{} firstTime := time.Now().Add(-time.Hour) secondTime := time.Now().Add(time.Hour) f1 := &fakeCollector{ nextCollectionTime: firstTime, } f2 := &fakeCollector{ nextCollectionTime: secondTime, } assert := assert.New(t) assert.NoError(cm.RegisterCollector(f1)) assert.NoError(cm.RegisterCollector(f2)) // First collection, everyone gets collected from. nextTime, _, err := cm.Collect() assert.Equal(firstTime, nextTime) assert.NoError(err) assert.Equal(1, f1.collectedFrom) assert.Equal(1, f2.collectedFrom) f1.nextCollectionTime = time.Now().Add(2 * time.Hour) // Second collection, only the one that is ready gets collected from. nextTime, _, err = cm.Collect() assert.Equal(secondTime, nextTime) assert.NoError(err) assert.Equal(2, f1.collectedFrom) assert.Equal(1, f2.collectedFrom) } cadvisor-0.27.1/collector/config.go000066400000000000000000000053451315410276000172030ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package collector import ( "time" "encoding/json" "github.com/google/cadvisor/info/v1" ) type Config struct { // the endpoint to hit to scrape metrics Endpoint EndpointConfig `json:"endpoint"` // holds information about different metrics that can be collected MetricsConfig []MetricConfig `json:"metrics_config"` } // metricConfig holds information extracted from the config file about a metric type MetricConfig struct { // the name of the metric Name string `json:"name"` // enum type for the metric type MetricType v1.MetricType `json:"metric_type"` // metric units to display on UI and in storage (eg: MB, cores) // this is only used for display. Units string `json:"units"` // data type of the metric (eg: int, float) DataType v1.DataType `json:"data_type"` // the frequency at which the metric should be collected PollingFrequency time.Duration `json:"polling_frequency"` // the regular expression that can be used to extract the metric Regex string `json:"regex"` } type Prometheus struct { // the endpoint to hit to scrape metrics Endpoint EndpointConfig `json:"endpoint"` // the frequency at which metrics should be collected PollingFrequency time.Duration `json:"polling_frequency"` // holds names of different metrics that can be collected MetricsConfig []string `json:"metrics_config"` } type EndpointConfig struct { // The full URL of the endpoint to reach URL string // A configuration in which an actual URL is constructed from, using the container's ip address URLConfig URLConfig } type URLConfig struct { // the protocol to use for connecting to the endpoint. Eg 'http' or 'https' Protocol string `json:"protocol"` // the port to use for connecting to the endpoint. Eg '8778' Port json.Number `json:"port"` // the path to use for the endpoint. Eg '/metrics' Path string `json:"path"` } func (ec *EndpointConfig) UnmarshalJSON(b []byte) error { url := "" config := URLConfig{ Protocol: "http", Port: "8000", } if err := json.Unmarshal(b, &url); err == nil { ec.URL = url return nil } err := json.Unmarshal(b, &config) if err == nil { ec.URLConfig = config return nil } return err } cadvisor-0.27.1/collector/config/000077500000000000000000000000001315410276000166455ustar00rootroot00000000000000cadvisor-0.27.1/collector/config/sample_config.json000066400000000000000000000020151315410276000223440ustar00rootroot00000000000000{ "endpoint" : "http://localhost:8000/nginx_status", "metrics_config" : [ { "name" : "activeConnections", "metric_type" : "gauge", "units" : "number of active connections", "data_type" : "int", "polling_frequency" : 10, "regex" : "Active connections: ([0-9]+)" }, { "name" : "reading", "metric_type" : "gauge", "units" : "number of reading connections", "data_type" : "int", "polling_frequency" : 10, "regex" : "Reading: ([0-9]+) .*" }, { "name" : "writing", "metric_type" : "gauge", "data_type" : "int", "units" : "number of writing connections", "polling_frequency" : 10, "regex" : ".*Writing: ([0-9]+).*" }, { "name" : "waiting", "metric_type" : "gauge", "units" : "number of waiting connections", "data_type" : "int", "polling_frequency" : 10, "regex" : ".*Waiting: ([0-9]+)" } ] } cadvisor-0.27.1/collector/config/sample_config_endpoint_config.json000066400000000000000000000017321315410276000255760ustar00rootroot00000000000000{ "endpoint" : { "protocol": "https", "port": 8000, "path": "/nginx_status" }, "metrics_config" : [ { "name" : "activeConnections", "metric_type" : "gauge", "units" : "number of active connections", "data_type" : "int", "polling_frequency" : 10, "regex" : "Active connections: ([0-9]+)" }, { "name" : "reading", "metric_type" : "gauge", "units" : "number of reading connections", "data_type" : "int", "polling_frequency" : 10, "regex" : "Reading: ([0-9]+) .*" }, { "name" : "writing", "metric_type" : "gauge", "data_type" : "int", "units" : "number of writing connections", "polling_frequency" : 10, "regex" : ".*Writing: ([0-9]+).*" }, { "name" : "waiting", "metric_type" : "gauge", "units" : "number of waiting connections", "data_type" : "int", "polling_frequency" : 10, "regex" : ".*Waiting: ([0-9]+)" } ] } cadvisor-0.27.1/collector/config/sample_config_prometheus.json000066400000000000000000000001751315410276000246240ustar00rootroot00000000000000{ "endpoint" : "http://localhost:8080/metrics", "polling_frequency" : 10, "metrics_config" : [ ] } cadvisor-0.27.1/collector/config/sample_config_prometheus_endpoint_config.json000066400000000000000000000002211315410276000300410ustar00rootroot00000000000000{ "endpoint" : { "protocol": "http", "port": 8081, "path": "/METRICS" }, "polling_frequency" : 10, "metrics_config" : [ ] }cadvisor-0.27.1/collector/config/sample_config_prometheus_filtered.json000066400000000000000000000002631315410276000265000ustar00rootroot00000000000000{ "endpoint" : "http://localhost:8080/metrics", "polling_frequency" : 10, "metrics_config" : [ "go_goroutines", "qps" ] } cadvisor-0.27.1/collector/fakes.go000066400000000000000000000021101315410276000170120ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package collector import ( "time" "github.com/google/cadvisor/info/v1" ) type FakeCollectorManager struct { } func (fkm *FakeCollectorManager) RegisterCollector(collector Collector) error { return nil } func (fkm *FakeCollectorManager) GetSpec() ([]v1.MetricSpec, error) { return []v1.MetricSpec{}, nil } func (fkm *FakeCollectorManager) Collect(metric map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) { var zero time.Time return zero, metric, nil } cadvisor-0.27.1/collector/generic_collector.go000066400000000000000000000130301315410276000214060ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package collector import ( "encoding/json" "fmt" "io/ioutil" "net/http" "regexp" "strconv" "strings" "time" "github.com/google/cadvisor/container" "github.com/google/cadvisor/info/v1" ) type GenericCollector struct { // name of the collector name string // holds information extracted from the config file for a collector configFile Config // holds information necessary to extract metrics info *collectorInfo // The Http client to use when connecting to metric endpoints httpClient *http.Client } type collectorInfo struct { // minimum polling frequency among all metrics minPollingFrequency time.Duration // regular expresssions for all metrics regexps []*regexp.Regexp // Limit for the number of srcaped metrics. If the count is higher, // no metrics will be returned. metricCountLimit int } // Returns a new collector using the information extracted from the configfile func NewCollector(collectorName string, configFile []byte, metricCountLimit int, containerHandler container.ContainerHandler, httpClient *http.Client) (*GenericCollector, error) { var configInJSON Config err := json.Unmarshal(configFile, &configInJSON) if err != nil { return nil, err } configInJSON.Endpoint.configure(containerHandler) // TODO : Add checks for validity of config file (eg : Accurate JSON fields) if len(configInJSON.MetricsConfig) == 0 { return nil, fmt.Errorf("No metrics provided in config") } minPollFrequency := time.Duration(0) regexprs := make([]*regexp.Regexp, len(configInJSON.MetricsConfig)) for ind, metricConfig := range configInJSON.MetricsConfig { // Find the minimum specified polling frequency in metric config. if metricConfig.PollingFrequency != 0 { if minPollFrequency == 0 || metricConfig.PollingFrequency < minPollFrequency { minPollFrequency = metricConfig.PollingFrequency } } regexprs[ind], err = regexp.Compile(metricConfig.Regex) if err != nil { return nil, fmt.Errorf("Invalid regexp %v for metric %v", metricConfig.Regex, metricConfig.Name) } } // Minimum supported polling frequency is 1s. minSupportedFrequency := 1 * time.Second if minPollFrequency < minSupportedFrequency { minPollFrequency = minSupportedFrequency } if len(configInJSON.MetricsConfig) > metricCountLimit { return nil, fmt.Errorf("Too many metrics defined: %d limit: %d", len(configInJSON.MetricsConfig), metricCountLimit) } return &GenericCollector{ name: collectorName, configFile: configInJSON, info: &collectorInfo{ minPollingFrequency: minPollFrequency, regexps: regexprs, metricCountLimit: metricCountLimit, }, httpClient: httpClient, }, nil } // Returns name of the collector func (collector *GenericCollector) Name() string { return collector.name } func (collector *GenericCollector) configToSpec(config MetricConfig) v1.MetricSpec { return v1.MetricSpec{ Name: config.Name, Type: config.MetricType, Format: config.DataType, Units: config.Units, } } func (collector *GenericCollector) GetSpec() []v1.MetricSpec { specs := []v1.MetricSpec{} for _, metricConfig := range collector.configFile.MetricsConfig { spec := collector.configToSpec(metricConfig) specs = append(specs, spec) } return specs } // Returns collected metrics and the next collection time of the collector func (collector *GenericCollector) Collect(metrics map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) { currentTime := time.Now() nextCollectionTime := currentTime.Add(time.Duration(collector.info.minPollingFrequency)) uri := collector.configFile.Endpoint.URL response, err := collector.httpClient.Get(uri) if err != nil { return nextCollectionTime, nil, err } defer response.Body.Close() pageContent, err := ioutil.ReadAll(response.Body) if err != nil { return nextCollectionTime, nil, err } var errorSlice []error for ind, metricConfig := range collector.configFile.MetricsConfig { matchString := collector.info.regexps[ind].FindStringSubmatch(string(pageContent)) if matchString != nil { if metricConfig.DataType == v1.FloatType { regVal, err := strconv.ParseFloat(strings.TrimSpace(matchString[1]), 64) if err != nil { errorSlice = append(errorSlice, err) } metrics[metricConfig.Name] = []v1.MetricVal{ {FloatValue: regVal, Timestamp: currentTime}, } } else if metricConfig.DataType == v1.IntType { regVal, err := strconv.ParseInt(strings.TrimSpace(matchString[1]), 10, 64) if err != nil { errorSlice = append(errorSlice, err) } metrics[metricConfig.Name] = []v1.MetricVal{ {IntValue: regVal, Timestamp: currentTime}, } } else { errorSlice = append(errorSlice, fmt.Errorf("Unexpected value of 'data_type' for metric '%v' in config ", metricConfig.Name)) } } else { errorSlice = append(errorSlice, fmt.Errorf("No match found for regexp: %v for metric '%v' in config", metricConfig.Regex, metricConfig.Name)) } } return nextCollectionTime, metrics, compileErrors(errorSlice) } cadvisor-0.27.1/collector/generic_collector_test.go000066400000000000000000000154251315410276000224570ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package collector import ( "fmt" "io/ioutil" "net/http" "net/http/httptest" "os" "testing" "github.com/google/cadvisor/info/v1" containertest "github.com/google/cadvisor/container/testing" "github.com/stretchr/testify/assert" ) func TestEmptyConfig(t *testing.T) { assert := assert.New(t) emptyConfig := ` { "endpoint" : "http://localhost:8000/nginx_status", "metrics_config" : [ ] } ` // Create a temporary config file 'temp.json' with invalid json format assert.NoError(ioutil.WriteFile("temp.json", []byte(emptyConfig), 0777)) configFile, err := ioutil.ReadFile("temp.json") assert.NoError(err) containerHandler := containertest.NewMockContainerHandler("mockContainer") _, err = NewCollector("tempCollector", configFile, 100, containerHandler, http.DefaultClient) assert.Error(err) assert.NoError(os.Remove("temp.json")) } func TestConfigWithErrors(t *testing.T) { assert := assert.New(t) // Syntax error: Missed '"' after activeConnections invalid := ` { "endpoint" : "http://localhost:8000/nginx_status", "metrics_config" : [ { "name" : "activeConnections, "metric_type" : "gauge", "data_type" : "int", "polling_frequency" : 10, "regex" : "Active connections: ([0-9]+)" } ] } ` // Create a temporary config file 'temp.json' with invalid json format assert.NoError(ioutil.WriteFile("temp.json", []byte(invalid), 0777)) configFile, err := ioutil.ReadFile("temp.json") assert.NoError(err) containerHandler := containertest.NewMockContainerHandler("mockContainer") _, err = NewCollector("tempCollector", configFile, 100, containerHandler, http.DefaultClient) assert.Error(err) assert.NoError(os.Remove("temp.json")) } func TestConfigWithRegexErrors(t *testing.T) { assert := assert.New(t) // Error: Missed operand for '+' in activeConnections regex invalid := ` { "endpoint" : "host:port/nginx_status", "metrics_config" : [ { "name" : "activeConnections", "metric_type" : "gauge", "data_type" : "int", "polling_frequency" : 10, "regex" : "Active connections: (+)" }, { "name" : "reading", "metric_type" : "gauge", "data_type" : "int", "polling_frequency" : 10, "regex" : "Reading: ([0-9]+) .*" } ] } ` // Create a temporary config file 'temp.json' assert.NoError(ioutil.WriteFile("temp.json", []byte(invalid), 0777)) configFile, err := ioutil.ReadFile("temp.json") assert.NoError(err) containerHandler := containertest.NewMockContainerHandler("mockContainer") _, err = NewCollector("tempCollector", configFile, 100, containerHandler, http.DefaultClient) assert.Error(err) assert.NoError(os.Remove("temp.json")) } func TestConfig(t *testing.T) { assert := assert.New(t) // Create an nginx collector using the config file 'sample_config.json' configFile, err := ioutil.ReadFile("config/sample_config.json") assert.NoError(err) containerHandler := containertest.NewMockContainerHandler("mockContainer") collector, err := NewCollector("nginx", configFile, 100, containerHandler, http.DefaultClient) assert.NoError(err) assert.Equal(collector.name, "nginx") assert.Equal(collector.configFile.Endpoint.URL, "http://localhost:8000/nginx_status") assert.Equal(collector.configFile.MetricsConfig[0].Name, "activeConnections") } func TestEndpointConfig(t *testing.T) { assert := assert.New(t) configFile, err := ioutil.ReadFile("config/sample_config_endpoint_config.json") assert.NoError(err) containerHandler := containertest.NewMockContainerHandler("mockContainer") containerHandler.On("GetContainerIPAddress").Return( "111.111.111.111", ) collector, err := NewCollector("nginx", configFile, 100, containerHandler, http.DefaultClient) assert.NoError(err) assert.Equal(collector.name, "nginx") assert.Equal(collector.configFile.Endpoint.URL, "https://111.111.111.111:8000/nginx_status") assert.Equal(collector.configFile.MetricsConfig[0].Name, "activeConnections") } func TestMetricCollection(t *testing.T) { assert := assert.New(t) // Collect nginx metrics from a fake nginx endpoint configFile, err := ioutil.ReadFile("config/sample_config.json") assert.NoError(err) containerHandler := containertest.NewMockContainerHandler("mockContainer") fakeCollector, err := NewCollector("nginx", configFile, 100, containerHandler, http.DefaultClient) assert.NoError(err) tempServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Active connections: 3\nserver accepts handled requests") fmt.Fprintln(w, "5 5 32\nReading: 0 Writing: 1 Waiting: 2") })) defer tempServer.Close() fakeCollector.configFile.Endpoint.URL = tempServer.URL metrics := map[string][]v1.MetricVal{} _, metrics, errMetric := fakeCollector.Collect(metrics) assert.NoError(errMetric) metricNames := []string{"activeConnections", "reading", "writing", "waiting"} // activeConnections = 3 assert.Equal(metrics[metricNames[0]][0].IntValue, 3) assert.Equal(metrics[metricNames[0]][0].FloatValue, 0) // reading = 0 assert.Equal(metrics[metricNames[1]][0].IntValue, 0) assert.Equal(metrics[metricNames[1]][0].FloatValue, 0) // writing = 1 assert.Equal(metrics[metricNames[2]][0].IntValue, 1) assert.Equal(metrics[metricNames[2]][0].FloatValue, 0) // waiting = 2 assert.Equal(metrics[metricNames[3]][0].IntValue, 2) assert.Equal(metrics[metricNames[3]][0].FloatValue, 0) } func TestMetricCollectionLimit(t *testing.T) { assert := assert.New(t) // Collect nginx metrics from a fake nginx endpoint configFile, err := ioutil.ReadFile("config/sample_config.json") assert.NoError(err) containerHandler := containertest.NewMockContainerHandler("mockContainer") _, err = NewCollector("nginx", configFile, 1, containerHandler, http.DefaultClient) assert.Error(err) } cadvisor-0.27.1/collector/prometheus_collector.go000066400000000000000000000171311315410276000221730ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package collector import ( "bytes" "encoding/json" "fmt" "io" "net/http" "sort" "time" rawmodel "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "github.com/prometheus/common/model" "github.com/google/cadvisor/container" "github.com/google/cadvisor/info/v1" ) type PrometheusCollector struct { // name of the collector name string // rate at which metrics are collected pollingFrequency time.Duration // holds information extracted from the config file for a collector configFile Prometheus // the metrics to gather (uses a map as a set) metricsSet map[string]bool // Limit for the number of scaped metrics. If the count is higher, // no metrics will be returned. metricCountLimit int // The Http client to use when connecting to metric endpoints httpClient *http.Client } // Returns a new collector using the information extracted from the configfile func NewPrometheusCollector(collectorName string, configFile []byte, metricCountLimit int, containerHandler container.ContainerHandler, httpClient *http.Client) (*PrometheusCollector, error) { var configInJSON Prometheus err := json.Unmarshal(configFile, &configInJSON) if err != nil { return nil, err } configInJSON.Endpoint.configure(containerHandler) minPollingFrequency := configInJSON.PollingFrequency // Minimum supported frequency is 1s minSupportedFrequency := 1 * time.Second if minPollingFrequency < minSupportedFrequency { minPollingFrequency = minSupportedFrequency } if metricCountLimit < 0 { return nil, fmt.Errorf("Metric count limit must be greater than or equal to 0") } var metricsSet map[string]bool if len(configInJSON.MetricsConfig) > 0 { metricsSet = make(map[string]bool, len(configInJSON.MetricsConfig)) for _, name := range configInJSON.MetricsConfig { metricsSet[name] = true } } if len(configInJSON.MetricsConfig) > metricCountLimit { return nil, fmt.Errorf("Too many metrics defined: %d limit %d", len(configInJSON.MetricsConfig), metricCountLimit) } // TODO : Add checks for validity of config file (eg : Accurate JSON fields) return &PrometheusCollector{ name: collectorName, pollingFrequency: minPollingFrequency, configFile: configInJSON, metricsSet: metricsSet, metricCountLimit: metricCountLimit, httpClient: httpClient, }, nil } // Returns name of the collector func (collector *PrometheusCollector) Name() string { return collector.name } func (collector *PrometheusCollector) GetSpec() []v1.MetricSpec { response, err := collector.httpClient.Get(collector.configFile.Endpoint.URL) if err != nil { return nil } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil } dec := expfmt.NewDecoder(response.Body, expfmt.ResponseFormat(response.Header)) var specs []v1.MetricSpec for { d := rawmodel.MetricFamily{} if err = dec.Decode(&d); err != nil { break } name := d.GetName() if len(name) == 0 { continue } // If metrics to collect is specified, skip any metrics not in the list to collect. if _, ok := collector.metricsSet[name]; collector.metricsSet != nil && !ok { continue } spec := v1.MetricSpec{ Name: name, Type: metricType(d.GetType()), Format: v1.FloatType, } specs = append(specs, spec) } if err != nil && err != io.EOF { return nil } return specs } // metricType converts Prometheus metric type to cadvisor metric type. // If there is no mapping then just return the name of the Prometheus metric type. func metricType(t rawmodel.MetricType) v1.MetricType { switch t { case rawmodel.MetricType_COUNTER: return v1.MetricCumulative case rawmodel.MetricType_GAUGE: return v1.MetricGauge default: return v1.MetricType(t.String()) } } type prometheusLabels []*rawmodel.LabelPair func labelSetToLabelPairs(labels model.Metric) prometheusLabels { var promLabels prometheusLabels for k, v := range labels { name := string(k) value := string(v) promLabels = append(promLabels, &rawmodel.LabelPair{Name: &name, Value: &value}) } return promLabels } func (s prometheusLabels) Len() int { return len(s) } func (s prometheusLabels) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // ByName implements sort.Interface by providing Less and using the Len and // Swap methods of the embedded PrometheusLabels value. type byName struct{ prometheusLabels } func (s byName) Less(i, j int) bool { return s.prometheusLabels[i].GetName() < s.prometheusLabels[j].GetName() } func prometheusLabelSetToCadvisorLabel(promLabels model.Metric) string { labels := labelSetToLabelPairs(promLabels) sort.Sort(byName{labels}) var b bytes.Buffer for i, l := range labels { if i > 0 { b.WriteString("\xff") } b.WriteString(l.GetName()) b.WriteString("=") b.WriteString(l.GetValue()) } return string(b.Bytes()) } // Returns collected metrics and the next collection time of the collector func (collector *PrometheusCollector) Collect(metrics map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) { currentTime := time.Now() nextCollectionTime := currentTime.Add(time.Duration(collector.pollingFrequency)) uri := collector.configFile.Endpoint.URL response, err := collector.httpClient.Get(uri) if err != nil { return nextCollectionTime, nil, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nextCollectionTime, nil, fmt.Errorf("server returned HTTP status %s", response.Status) } sdec := expfmt.SampleDecoder{ Dec: expfmt.NewDecoder(response.Body, expfmt.ResponseFormat(response.Header)), Opts: &expfmt.DecodeOptions{ Timestamp: model.TimeFromUnixNano(currentTime.UnixNano()), }, } var ( // 50 is chosen as a reasonable guesstimate at a number of metrics we can // expect from virtually any endpoint to try to save allocations. decSamples = make(model.Vector, 0, 50) newMetrics = make(map[string][]v1.MetricVal) ) for { if err = sdec.Decode(&decSamples); err != nil { break } for _, sample := range decSamples { metName := string(sample.Metric[model.MetricNameLabel]) if len(metName) == 0 { continue } // If metrics to collect is specified, skip any metrics not in the list to collect. if _, ok := collector.metricsSet[metName]; collector.metricsSet != nil && !ok { continue } // TODO Handle multiple labels nicer. Prometheus metrics can have multiple // labels, cadvisor only accepts a single string for the metric label. label := prometheusLabelSetToCadvisorLabel(sample.Metric) metric := v1.MetricVal{ FloatValue: float64(sample.Value), Timestamp: sample.Timestamp.Time(), Label: label, } newMetrics[metName] = append(newMetrics[metName], metric) if len(newMetrics) > collector.metricCountLimit { return nextCollectionTime, nil, fmt.Errorf("too many metrics to collect") } } decSamples = decSamples[:0] } if err != nil && err != io.EOF { return nextCollectionTime, nil, err } for key, val := range newMetrics { metrics[key] = append(metrics[key], val...) } return nextCollectionTime, metrics, nil } cadvisor-0.27.1/collector/prometheus_collector_test.go000066400000000000000000000215401315410276000232310ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package collector import ( "fmt" "io/ioutil" "net/http" "net/http/httptest" "testing" "github.com/google/cadvisor/info/v1" containertest "github.com/google/cadvisor/container/testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPrometheus(t *testing.T) { assert := assert.New(t) // Create a prometheus collector using the config file 'sample_config_prometheus.json' configFile, err := ioutil.ReadFile("config/sample_config_prometheus.json") containerHandler := containertest.NewMockContainerHandler("mockContainer") collector, err := NewPrometheusCollector("Prometheus", configFile, 100, containerHandler, http.DefaultClient) assert.NoError(err) assert.Equal("Prometheus", collector.name) assert.Equal("http://localhost:8080/metrics", collector.configFile.Endpoint.URL) tempServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { text := `# HELP go_gc_duration_seconds A summary of the GC invocation durations. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 5.8348000000000004e-05 go_gc_duration_seconds{quantile="1"} 0.000499764 go_gc_duration_seconds_sum 1.7560473e+07 go_gc_duration_seconds_count 2693 # HELP go_goroutines Number of goroutines that currently exist. # TYPE go_goroutines gauge go_goroutines 16 # HELP empty_metric A metric without any values # TYPE empty_metric counter # HELP metric_with_spaces_in_label A metric with spaces in a label. # TYPE metric_with_spaces_in_label gauge metric_with_spaces_in_label{name="Network Agent"} 72 # HELP metric_with_multiple_labels A metric with multiple labels. # TYPE metric_with_multiple_labels gauge metric_with_multiple_labels{label1="One", label2="Two", label3="Three"} 81 ` fmt.Fprintln(w, text) })) defer tempServer.Close() collector.configFile.Endpoint.URL = tempServer.URL var spec []v1.MetricSpec require.NotPanics(t, func() { spec = collector.GetSpec() }) assert.Len(spec, 4) specNames := make(map[string]struct{}, 3) for _, s := range spec { specNames[s.Name] = struct{}{} } expectedSpecNames := map[string]struct{}{ "go_gc_duration_seconds": {}, "go_goroutines": {}, "metric_with_spaces_in_label": {}, "metric_with_multiple_labels": {}, } assert.Equal(expectedSpecNames, specNames) metrics := map[string][]v1.MetricVal{} _, metrics, errMetric := collector.Collect(metrics) assert.NoError(errMetric) go_gc_duration := metrics["go_gc_duration_seconds"] assert.Equal(5.8348000000000004e-05, go_gc_duration[0].FloatValue) assert.Equal("__name__=go_gc_duration_seconds\xffquantile=0", go_gc_duration[0].Label) assert.Equal(0.000499764, go_gc_duration[1].FloatValue) assert.Equal("__name__=go_gc_duration_seconds\xffquantile=1", go_gc_duration[1].Label) go_gc_duration_sum := metrics["go_gc_duration_seconds_sum"] assert.Equal(1.7560473e+07, go_gc_duration_sum[0].FloatValue) assert.Equal("__name__=go_gc_duration_seconds_sum", go_gc_duration_sum[0].Label) go_gc_duration_count := metrics["go_gc_duration_seconds_count"] assert.Equal(2693, go_gc_duration_count[0].FloatValue) assert.Equal("__name__=go_gc_duration_seconds_count", go_gc_duration_count[0].Label) goRoutines := metrics["go_goroutines"] assert.Equal(16, goRoutines[0].FloatValue) assert.Equal("__name__=go_goroutines", goRoutines[0].Label) metricWithSpaces := metrics["metric_with_spaces_in_label"] assert.Equal(72, metricWithSpaces[0].FloatValue) assert.Equal("__name__=metric_with_spaces_in_label\xffname=Network Agent", metricWithSpaces[0].Label) metricWithMultipleLabels := metrics["metric_with_multiple_labels"] assert.Equal(81, metricWithMultipleLabels[0].FloatValue) assert.Equal("__name__=metric_with_multiple_labels\xfflabel1=One\xfflabel2=Two\xfflabel3=Three", metricWithMultipleLabels[0].Label) } func TestPrometheusEndpointConfig(t *testing.T) { assert := assert.New(t) //Create a prometheus collector using the config file 'sample_config_prometheus.json' configFile, err := ioutil.ReadFile("config/sample_config_prometheus_endpoint_config.json") containerHandler := containertest.NewMockContainerHandler("mockContainer") containerHandler.On("GetContainerIPAddress").Return( "222.222.222.222", ) collector, err := NewPrometheusCollector("Prometheus", configFile, 100, containerHandler, http.DefaultClient) assert.NoError(err) assert.Equal(collector.name, "Prometheus") assert.Equal(collector.configFile.Endpoint.URL, "http://222.222.222.222:8081/METRICS") } func TestPrometheusShortResponse(t *testing.T) { assert := assert.New(t) // Create a prometheus collector using the config file 'sample_config_prometheus.json' configFile, err := ioutil.ReadFile("config/sample_config_prometheus.json") containerHandler := containertest.NewMockContainerHandler("mockContainer") collector, err := NewPrometheusCollector("Prometheus", configFile, 100, containerHandler, http.DefaultClient) assert.NoError(err) assert.Equal(collector.name, "Prometheus") assert.Equal(collector.configFile.Endpoint.URL, "http://localhost:8080/metrics") tempServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { text := "# HELP empty_metric A metric without any values" fmt.Fprint(w, text) })) defer tempServer.Close() collector.configFile.Endpoint.URL = tempServer.URL assert.NotPanics(func() { collector.GetSpec() }) } func TestPrometheusMetricCountLimit(t *testing.T) { assert := assert.New(t) // Create a prometheus collector using the config file 'sample_config_prometheus.json' configFile, err := ioutil.ReadFile("config/sample_config_prometheus.json") containerHandler := containertest.NewMockContainerHandler("mockContainer") collector, err := NewPrometheusCollector("Prometheus", configFile, 10, containerHandler, http.DefaultClient) assert.NoError(err) assert.Equal(collector.name, "Prometheus") assert.Equal(collector.configFile.Endpoint.URL, "http://localhost:8080/metrics") tempServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for i := 0; i < 30; i++ { fmt.Fprintf(w, "# HELP m%d Number of goroutines that currently exist.\n", i) fmt.Fprintf(w, "# TYPE m%d gauge\n", i) fmt.Fprintf(w, "m%d %d", i, i) } })) defer tempServer.Close() collector.configFile.Endpoint.URL = tempServer.URL metrics := map[string][]v1.MetricVal{} _, result, errMetric := collector.Collect(metrics) assert.Error(errMetric) assert.Equal(len(metrics), 0) assert.Nil(result) } func TestPrometheusFiltersMetrics(t *testing.T) { assert := assert.New(t) // Create a prometheus collector using the config file 'sample_config_prometheus_filtered.json' configFile, err := ioutil.ReadFile("config/sample_config_prometheus_filtered.json") containerHandler := containertest.NewMockContainerHandler("mockContainer") collector, err := NewPrometheusCollector("Prometheus", configFile, 100, containerHandler, http.DefaultClient) assert.NoError(err) assert.Equal(collector.name, "Prometheus") assert.Equal(collector.configFile.Endpoint.URL, "http://localhost:8080/metrics") tempServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { text := `# HELP go_gc_duration_seconds A summary of the GC invocation durations. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 5.8348000000000004e-05 go_gc_duration_seconds{quantile="1"} 0.000499764 go_gc_duration_seconds_sum 1.7560473e+07 go_gc_duration_seconds_count 2693 # HELP go_goroutines Number of goroutines that currently exist. # TYPE go_goroutines gauge go_goroutines 16 ` fmt.Fprintln(w, text) })) defer tempServer.Close() collector.configFile.Endpoint.URL = tempServer.URL metrics := map[string][]v1.MetricVal{} _, metrics, errMetric := collector.Collect(metrics) assert.NoError(errMetric) assert.Len(metrics, 1) goRoutines := metrics["go_goroutines"] assert.Equal(goRoutines[0].FloatValue, 16) } func TestPrometheusFiltersMetricsCountLimit(t *testing.T) { assert := assert.New(t) // Create a prometheus collector using the config file 'sample_config_prometheus_filtered.json' configFile, err := ioutil.ReadFile("config/sample_config_prometheus_filtered.json") containerHandler := containertest.NewMockContainerHandler("mockContainer") _, err = NewPrometheusCollector("Prometheus", configFile, 1, containerHandler, http.DefaultClient) assert.Error(err) } cadvisor-0.27.1/collector/types.go000066400000000000000000000034161315410276000170770ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package collector import ( "time" "github.com/google/cadvisor/info/v1" ) // TODO(vmarmol): Export to a custom metrics type when that is available. // Metric collector. type Collector interface { // Collect metrics from this collector. // Returns the next time this collector should be collected from. // Next collection time is always returned, even when an error occurs. // A collection time of zero means no more collection. Collect(map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) // Return spec for all metrics associated with this collector GetSpec() []v1.MetricSpec // Name of this collector. Name() string } // Manages and runs collectors. type CollectorManager interface { // Register a collector. RegisterCollector(collector Collector) error // Collect from collectors that are ready and return the next time // at which a collector will be ready to collect from. // Next collection time is always returned, even when an error occurs. // A collection time of zero means no more collection. Collect() (time.Time, map[string][]v1.MetricVal, error) // Get metric spec from all registered collectors. GetSpec() ([]v1.MetricSpec, error) } cadvisor-0.27.1/collector/util.go000066400000000000000000000021341315410276000167040ustar00rootroot00000000000000// Copyright 2016 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 collector import "github.com/google/cadvisor/container" func (endpointConfig *EndpointConfig) configure(containerHandler container.ContainerHandler) { //If the exact URL was not specified, generate it based on the ip address of the container. endpoint := endpointConfig if endpoint.URL == "" { ipAddress := containerHandler.GetContainerIPAddress() endpointConfig.URL = endpoint.URLConfig.Protocol + "://" + ipAddress + ":" + endpoint.URLConfig.Port.String() + endpoint.URLConfig.Path } } cadvisor-0.27.1/container/000077500000000000000000000000001315410276000153745ustar00rootroot00000000000000cadvisor-0.27.1/container/common/000077500000000000000000000000001315410276000166645ustar00rootroot00000000000000cadvisor-0.27.1/container/common/container_hints.go000066400000000000000000000036261315410276000224110ustar00rootroot00000000000000// 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. // Unmarshal's a Containers description json file. The json file contains // an array of ContainerHint structs, each with a container's id and networkInterface // This allows collecting stats about network interfaces configured outside docker // and lxc package common import ( "encoding/json" "flag" "io/ioutil" "os" ) var ArgContainerHints = flag.String("container_hints", "/etc/cadvisor/container_hints.json", "location of the container hints file") type containerHints struct { AllHosts []containerHint `json:"all_hosts,omitempty"` } type containerHint struct { FullName string `json:"full_path,omitempty"` NetworkInterface *networkInterface `json:"network_interface,omitempty"` Mounts []Mount `json:"mounts,omitempty"` } type Mount struct { HostDir string `json:"host_dir,omitempty"` ContainerDir string `json:"container_dir,omitempty"` } type networkInterface struct { VethHost string `json:"veth_host,omitempty"` VethChild string `json:"veth_child,omitempty"` } func GetContainerHintsFromFile(containerHintsFile string) (containerHints, error) { dat, err := ioutil.ReadFile(containerHintsFile) if os.IsNotExist(err) { return containerHints{}, nil } var cHints containerHints if err == nil { err = json.Unmarshal(dat, &cHints) } return cHints, err } cadvisor-0.27.1/container/common/container_hints_test.go000066400000000000000000000032101315410276000234350ustar00rootroot00000000000000// 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 common import ( "testing" ) func TestGetContainerHintsFromFile(t *testing.T) { cHints, err := GetContainerHintsFromFile("test_resources/container_hints.json") if err != nil { t.Fatalf("Error in unmarshalling: %s", err) } if cHints.AllHosts[0].NetworkInterface.VethHost != "veth24031eth1" && cHints.AllHosts[0].NetworkInterface.VethChild != "eth1" { t.Errorf("Cannot find network interface in %s", cHints) } correctMountDirs := [...]string{ "/var/run/nm-sdc1", "/var/run/nm-sdb3", "/var/run/nm-sda3", "/var/run/netns/root", "/var/run/openvswitch/db.sock", } if len(cHints.AllHosts[0].Mounts) == 0 { t.Errorf("Cannot find any mounts") } for i, mountDir := range cHints.AllHosts[0].Mounts { if correctMountDirs[i] != mountDir.HostDir { t.Errorf("Cannot find mount %s in %s", mountDir.HostDir, cHints) } } } func TestFileNotExist(t *testing.T) { _, err := GetContainerHintsFromFile("/file_does_not_exist.json") if err != nil { t.Fatalf("GetContainerHintsFromFile must not error for blank file: %s", err) } } cadvisor-0.27.1/container/common/fsHandler.go000066400000000000000000000071721315410276000211300ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Handler for Docker containers. package common import ( "fmt" "sync" "time" "github.com/google/cadvisor/fs" "github.com/golang/glog" ) type FsHandler interface { Start() Usage() FsUsage Stop() } type FsUsage struct { BaseUsageBytes uint64 TotalUsageBytes uint64 InodeUsage uint64 } type realFsHandler struct { sync.RWMutex lastUpdate time.Time usage FsUsage period time.Duration minPeriod time.Duration rootfs string extraDir string fsInfo fs.FsInfo // Tells the container to stop. stopChan chan struct{} } const ( longOp = time.Second timeout = 2 * time.Minute maxBackoffFactor = 20 ) const DefaultPeriod = time.Minute var _ FsHandler = &realFsHandler{} func NewFsHandler(period time.Duration, rootfs, extraDir string, fsInfo fs.FsInfo) FsHandler { return &realFsHandler{ lastUpdate: time.Time{}, usage: FsUsage{}, period: period, minPeriod: period, rootfs: rootfs, extraDir: extraDir, fsInfo: fsInfo, stopChan: make(chan struct{}, 1), } } func (fh *realFsHandler) update() error { var ( baseUsage, extraDirUsage, inodeUsage uint64 rootDiskErr, rootInodeErr, extraDiskErr error ) // TODO(vishh): Add support for external mounts. if fh.rootfs != "" { baseUsage, rootDiskErr = fh.fsInfo.GetDirDiskUsage(fh.rootfs, timeout) inodeUsage, rootInodeErr = fh.fsInfo.GetDirInodeUsage(fh.rootfs, timeout) } if fh.extraDir != "" { extraDirUsage, extraDiskErr = fh.fsInfo.GetDirDiskUsage(fh.extraDir, timeout) } // Wait to handle errors until after all operartions are run. // An error in one will not cause an early return, skipping others fh.Lock() defer fh.Unlock() fh.lastUpdate = time.Now() if rootDiskErr == nil && fh.rootfs != "" { fh.usage.InodeUsage = inodeUsage } if rootInodeErr == nil && fh.rootfs != "" { fh.usage.TotalUsageBytes = baseUsage + extraDirUsage } if extraDiskErr == nil && fh.extraDir != "" { fh.usage.BaseUsageBytes = baseUsage } // Combine errors into a single error to return if rootDiskErr != nil || rootInodeErr != nil || extraDiskErr != nil { return fmt.Errorf("rootDiskErr: %v, rootInodeErr: %v, extraDiskErr: %v", rootDiskErr, rootInodeErr, extraDiskErr) } return nil } func (fh *realFsHandler) trackUsage() { fh.update() for { select { case <-fh.stopChan: return case <-time.After(fh.period): start := time.Now() if err := fh.update(); err != nil { glog.Errorf("failed to collect filesystem stats - %v", err) fh.period = fh.period * 2 if fh.period > maxBackoffFactor*fh.minPeriod { fh.period = maxBackoffFactor * fh.minPeriod } } else { fh.period = fh.minPeriod } duration := time.Since(start) if duration > longOp { glog.V(2).Infof("du and find on following dirs took %v: %v", duration, []string{fh.rootfs, fh.extraDir}) } } } } func (fh *realFsHandler) Start() { go fh.trackUsage() } func (fh *realFsHandler) Stop() { close(fh.stopChan) } func (fh *realFsHandler) Usage() FsUsage { fh.RLock() defer fh.RUnlock() return fh.usage } cadvisor-0.27.1/container/common/helpers.go000066400000000000000000000176661315410276000206750ustar00rootroot00000000000000// Copyright 2016 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 common import ( "fmt" "io/ioutil" "os" "path" "strconv" "strings" "time" "github.com/google/cadvisor/container" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/utils" "github.com/golang/glog" ) func DebugInfo(watches map[string][]string) map[string][]string { out := make(map[string][]string) lines := make([]string, 0, len(watches)) for containerName, cgroupWatches := range watches { lines = append(lines, fmt.Sprintf("%s:", containerName)) for _, cg := range cgroupWatches { lines = append(lines, fmt.Sprintf("\t%s", cg)) } } out["Inotify watches"] = lines return out } func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoFactory, hasNetwork, hasFilesystem bool) (info.ContainerSpec, error) { var spec info.ContainerSpec // Assume unified hierarchy containers. // Get the lowest creation time from all hierarchies as the container creation time. now := time.Now() lowestTime := now for _, cgroupPath := range cgroupPaths { // The modified time of the cgroup directory changes whenever a subcontainer is created. // eg. /docker will have creation time matching the creation of latest docker container. // Use clone_children as a workaround as it isn't usually modified. It is only likely changed // immediately after creating a container. cgroupPath = path.Join(cgroupPath, "cgroup.clone_children") fi, err := os.Stat(cgroupPath) if err == nil && fi.ModTime().Before(lowestTime) { lowestTime = fi.ModTime() } } if lowestTime != now { spec.CreationTime = lowestTime } // Get machine info. mi, err := machineInfoFactory.GetMachineInfo() if err != nil { return spec, err } // CPU. cpuRoot, ok := cgroupPaths["cpu"] if ok { if utils.FileExists(cpuRoot) { spec.HasCpu = true spec.Cpu.Limit = readUInt64(cpuRoot, "cpu.shares") spec.Cpu.Period = readUInt64(cpuRoot, "cpu.cfs_period_us") quota := readString(cpuRoot, "cpu.cfs_quota_us") if quota != "" && quota != "-1" { val, err := strconv.ParseUint(quota, 10, 64) if err != nil { glog.Errorf("GetSpec: Failed to parse CPUQuota from %q: %s", path.Join(cpuRoot, "cpu.cfs_quota_us"), err) } spec.Cpu.Quota = val } } } // Cpu Mask. // This will fail for non-unified hierarchies. We'll return the whole machine mask in that case. cpusetRoot, ok := cgroupPaths["cpuset"] if ok { if utils.FileExists(cpusetRoot) { spec.HasCpu = true mask := readString(cpusetRoot, "cpuset.cpus") spec.Cpu.Mask = utils.FixCpuMask(mask, mi.NumCores) } } // Memory memoryRoot, ok := cgroupPaths["memory"] if ok { if utils.FileExists(memoryRoot) { spec.HasMemory = true spec.Memory.Limit = readUInt64(memoryRoot, "memory.limit_in_bytes") spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.memsw.limit_in_bytes") spec.Memory.Reservation = readUInt64(memoryRoot, "memory.soft_limit_in_bytes") } } spec.HasNetwork = hasNetwork spec.HasFilesystem = hasFilesystem if blkioRoot, ok := cgroupPaths["blkio"]; ok && utils.FileExists(blkioRoot) { spec.HasDiskIo = true } return spec, nil } func readString(dirpath string, file string) string { cgroupFile := path.Join(dirpath, file) // Read out, err := ioutil.ReadFile(cgroupFile) if err != nil { // Ignore non-existent files if !os.IsNotExist(err) { glog.Errorf("readString: Failed to read %q: %s", cgroupFile, err) } return "" } return strings.TrimSpace(string(out)) } func readUInt64(dirpath string, file string) uint64 { out := readString(dirpath, file) if out == "" { return 0 } val, err := strconv.ParseUint(out, 10, 64) if err != nil { glog.Errorf("readUInt64: Failed to parse int %q from file %q: %s", out, path.Join(dirpath, file), err) return 0 } return val } // Lists all directories under "path" and outputs the results as children of "parent". func ListDirectories(dirpath string, parent string, recursive bool, output map[string]struct{}) error { entries, err := ioutil.ReadDir(dirpath) if err != nil { // Ignore if this hierarchy does not exist. if os.IsNotExist(err) { err = nil } return err } for _, entry := range entries { // We only grab directories. if entry.IsDir() { name := path.Join(parent, entry.Name()) output[name] = struct{}{} // List subcontainers if asked to. if recursive { err := ListDirectories(path.Join(dirpath, entry.Name()), name, true, output) if err != nil { return err } } } } return nil } func MakeCgroupPaths(mountPoints map[string]string, name string) map[string]string { cgroupPaths := make(map[string]string, len(mountPoints)) for key, val := range mountPoints { cgroupPaths[key] = path.Join(val, name) } return cgroupPaths } func CgroupExists(cgroupPaths map[string]string) bool { // If any cgroup exists, the container is still alive. for _, cgroupPath := range cgroupPaths { if utils.FileExists(cgroupPath) { return true } } return false } func ListContainers(name string, cgroupPaths map[string]string, listType container.ListType) ([]info.ContainerReference, error) { containers := make(map[string]struct{}) for _, cgroupPath := range cgroupPaths { err := ListDirectories(cgroupPath, name, listType == container.ListRecursive, containers) if err != nil { return nil, err } } // Make into container references. ret := make([]info.ContainerReference, 0, len(containers)) for cont := range containers { ret = append(ret, info.ContainerReference{ Name: cont, }) } return ret, nil } // AssignDeviceNamesToDiskStats assigns the Device field on the provided DiskIoStats by looking up // the device major and minor identifiers in the provided device namer. func AssignDeviceNamesToDiskStats(namer DeviceNamer, stats *info.DiskIoStats) { assignDeviceNamesToPerDiskStats( namer, stats.IoMerged, stats.IoQueued, stats.IoServiceBytes, stats.IoServiceTime, stats.IoServiced, stats.IoTime, stats.IoWaitTime, stats.Sectors, ) } // assignDeviceNamesToPerDiskStats looks up device names for the provided stats, caching names // if necessary. func assignDeviceNamesToPerDiskStats(namer DeviceNamer, diskStats ...[]info.PerDiskStats) { devices := make(deviceIdentifierMap) for _, stats := range diskStats { for i, stat := range stats { stats[i].Device = devices.Find(stat.Major, stat.Minor, namer) } } } // DeviceNamer returns string names for devices by their major and minor id. type DeviceNamer interface { // DeviceName returns the name of the device by its major and minor ids, or false if no // such device is recognized. DeviceName(major, minor uint64) (string, bool) } type MachineInfoNamer info.MachineInfo func (n *MachineInfoNamer) DeviceName(major, minor uint64) (string, bool) { for _, info := range n.DiskMap { if info.Major == major && info.Minor == minor { return "/dev/" + info.Name, true } } for _, info := range n.Filesystems { if info.DeviceMajor == major && info.DeviceMinor == minor { return info.Device, true } } return "", false } type deviceIdentifier struct { major uint64 minor uint64 } type deviceIdentifierMap map[deviceIdentifier]string // Find locates the device name by device identifier out of from, caching the result as necessary. func (m deviceIdentifierMap) Find(major, minor uint64, namer DeviceNamer) string { d := deviceIdentifier{major, minor} if s, ok := m[d]; ok { return s } s, _ := namer.DeviceName(major, minor) m[d] = s return s } cadvisor-0.27.1/container/common/inotify_watcher.go000066400000000000000000000067271315410276000224250ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package common import ( "sync" "golang.org/x/exp/inotify" ) // Watcher for container-related inotify events in the cgroup hierarchy. // // Implementation is thread-safe. type InotifyWatcher struct { // Underlying inotify watcher. watcher *inotify.Watcher // Map of containers being watched to cgroup paths watched for that container. containersWatched map[string]map[string]bool // Lock for all datastructure access. lock sync.Mutex } func NewInotifyWatcher() (*InotifyWatcher, error) { w, err := inotify.NewWatcher() if err != nil { return nil, err } return &InotifyWatcher{ watcher: w, containersWatched: make(map[string]map[string]bool), }, nil } // Add a watch to the specified directory. Returns if the container was already being watched. func (iw *InotifyWatcher) AddWatch(containerName, dir string) (bool, error) { iw.lock.Lock() defer iw.lock.Unlock() cgroupsWatched, alreadyWatched := iw.containersWatched[containerName] // Register an inotify notification. if !cgroupsWatched[dir] { err := iw.watcher.AddWatch(dir, inotify.IN_CREATE|inotify.IN_DELETE|inotify.IN_MOVE) if err != nil { return alreadyWatched, err } if cgroupsWatched == nil { cgroupsWatched = make(map[string]bool) } cgroupsWatched[dir] = true } // Record our watching of the container. if !alreadyWatched { iw.containersWatched[containerName] = cgroupsWatched } return alreadyWatched, nil } // Remove watch from the specified directory. Returns if this was the last watch on the specified container. func (iw *InotifyWatcher) RemoveWatch(containerName, dir string) (bool, error) { iw.lock.Lock() defer iw.lock.Unlock() // If we don't have a watch registed for this, just return. cgroupsWatched, ok := iw.containersWatched[containerName] if !ok { return false, nil } // Remove the inotify watch if it exists. if cgroupsWatched[dir] { err := iw.watcher.RemoveWatch(dir) if err != nil { return false, nil } delete(cgroupsWatched, dir) } // Remove the record if this is the last watch. if len(cgroupsWatched) == 0 { delete(iw.containersWatched, containerName) return true, nil } return false, nil } // Errors are returned on this channel. func (iw *InotifyWatcher) Error() chan error { return iw.watcher.Error } // Events are returned on this channel. func (iw *InotifyWatcher) Event() chan *inotify.Event { return iw.watcher.Event } // Closes the inotify watcher. func (iw *InotifyWatcher) Close() error { return iw.watcher.Close() } // Returns a map of containers to the cgroup paths being watched. func (iw *InotifyWatcher) GetWatches() map[string][]string { out := make(map[string][]string, len(iw.containersWatched)) for k, v := range iw.containersWatched { out[k] = mapToSlice(v) } return out } func mapToSlice(m map[string]bool) []string { out := make([]string, 0, len(m)) for k := range m { out = append(out, k) } return out } cadvisor-0.27.1/container/common/test_resources/000077500000000000000000000000001315410276000217355ustar00rootroot00000000000000cadvisor-0.27.1/container/common/test_resources/container_hints.json000066400000000000000000000020311315410276000260130ustar00rootroot00000000000000{ "name": "Container Hints", "description": "Container hints file", "all_hosts": [ { "network_interface": { "veth_child": "eth1", "veth_host": "veth24031eth1" }, "mounts": [ { "host_dir": "/var/run/nm-sdc1", "container_dir": "/var/run/nm-sdc1", "permission": "rw" }, { "host_dir": "/var/run/nm-sdb3", "container_dir": "/var/run/nm-sdb3", "permission": "rw" }, { "host_dir": "/var/run/nm-sda3", "container_dir": "/var/run/nm-sda3", "permission": "rw" }, { "host_dir": "/var/run/netns/root", "container_dir": "/var/run/netns/root", "permission": "ro" }, { "host_dir": "/var/run/openvswitch/db.sock", "container_dir": "/var/run/openvswitch/db.sock", "permission": "rw" } ], "full_path": "18a4585950db428e4d5a65c216a5d708d241254709626f4cb300ee963fb4b144" } ] } cadvisor-0.27.1/container/container.go000066400000000000000000000044501315410276000177100ustar00rootroot00000000000000// 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 defines types for sub-container events and also // defines an interface for container operation handlers. package container import info "github.com/google/cadvisor/info/v1" // ListType describes whether listing should be just for a // specific container or performed recursively. type ListType int const ( ListSelf ListType = iota ListRecursive ) type ContainerType int const ( ContainerTypeRaw ContainerType = iota ContainerTypeDocker ContainerTypeRkt ContainerTypeSystemd ContainerTypeCrio ) // Interface for container operation handlers. type ContainerHandler interface { // Returns the ContainerReference ContainerReference() (info.ContainerReference, error) // Returns container's isolation spec. GetSpec() (info.ContainerSpec, error) // Returns the current stats values of the container. GetStats() (*info.ContainerStats, error) // Returns the subcontainers of this container. ListContainers(listType ListType) ([]info.ContainerReference, error) // Returns the processes inside this container. ListProcesses(listType ListType) ([]int, error) // Returns absolute cgroup path for the requested resource. GetCgroupPath(resource string) (string, error) // Returns container labels, if available. GetContainerLabels() map[string]string // Returns the container's ip address, if available GetContainerIPAddress() string // Returns whether the container still exists. Exists() bool // Cleanup frees up any resources being held like fds or go routines, etc. Cleanup() // Start starts any necessary background goroutines - must be cleaned up in Cleanup(). // It is expected that most implementations will be a no-op. Start() // Type of handler Type() ContainerType } cadvisor-0.27.1/container/crio/000077500000000000000000000000001315410276000163305ustar00rootroot00000000000000cadvisor-0.27.1/container/crio/client.go000066400000000000000000000066621315410276000201470ustar00rootroot00000000000000// Copyright 2017 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 crio import ( "encoding/json" "fmt" "net" "net/http" "syscall" "time" ) const ( CrioSocket = "/var/run/crio.sock" maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) ) // Info represents CRI-O information as sent by the CRI-O server type Info struct { StorageDriver string `json:"storage_driver"` StorageRoot string `json:"storage_root"` } // ContainerInfo represents a given container information type ContainerInfo struct { Name string `json:"name"` Pid int `json:"pid"` Image string `json:"image"` CreatedTime int64 `json:"created_time"` Labels map[string]string `json:"labels"` Annotations map[string]string `json:"annotations"` LogPath string `json:"log_path"` Root string `json:"root"` IP string `json:"ip_address"` } type crioClient interface { Info() (Info, error) ContainerInfo(string) (*ContainerInfo, error) } type crioClientImpl struct { client *http.Client } func configureUnixTransport(tr *http.Transport, proto, addr string) error { if len(addr) > maxUnixSocketPathSize { return fmt.Errorf("Unix socket path %q is too long", addr) } // No need for compression in local communications. tr.DisableCompression = true tr.Dial = func(_, _ string) (net.Conn, error) { return net.DialTimeout(proto, addr, 32*time.Second) } return nil } // Client returns a new configured CRI-O client func Client() (crioClient, error) { tr := new(http.Transport) configureUnixTransport(tr, "unix", CrioSocket) c := &http.Client{ Transport: tr, } return &crioClientImpl{ client: c, }, nil } func getRequest(path string) (*http.Request, error) { req, err := http.NewRequest("GET", path, nil) if err != nil { return nil, err } // For local communications over a unix socket, it doesn't matter what // the host is. We just need a valid and meaningful host name. req.Host = "crio" req.URL.Host = CrioSocket req.URL.Scheme = "http" return req, nil } // Info returns generic info from the CRI-O server func (c *crioClientImpl) Info() (Info, error) { info := Info{} req, err := getRequest("/info") if err != nil { return info, err } resp, err := c.client.Do(req) if err != nil { return info, err } defer resp.Body.Close() if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { return info, err } return info, nil } // ContainerInfo returns information about a given container func (c *crioClientImpl) ContainerInfo(id string) (*ContainerInfo, error) { req, err := getRequest("/containers/" + id) if err != nil { return nil, err } resp, err := c.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() cInfo := ContainerInfo{} if err := json.NewDecoder(resp.Body).Decode(&cInfo); err != nil { return nil, err } return &cInfo, nil } cadvisor-0.27.1/container/crio/client_test.go000066400000000000000000000024531315410276000212000ustar00rootroot00000000000000// Copyright 2017 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 crio import "fmt" type crioClientMock struct { info Info containersInfo map[string]*ContainerInfo err error } func (c *crioClientMock) Info() (Info, error) { if c.err != nil { return Info{}, c.err } return c.info, nil } func (c *crioClientMock) ContainerInfo(id string) (*ContainerInfo, error) { if c.err != nil { return nil, c.err } cInfo, ok := c.containersInfo[id] if !ok { return nil, fmt.Errorf("no container with id %s", id) } return cInfo, nil } func mockCrioClient(info Info, containersInfo map[string]*ContainerInfo, err error) crioClient { return &crioClientMock{ err: err, info: info, containersInfo: containersInfo, } } cadvisor-0.27.1/container/crio/factory.go000066400000000000000000000111021315410276000203210ustar00rootroot00000000000000// Copyright 2017 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 crio import ( "fmt" "path" "regexp" "strings" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/manager/watcher" "github.com/golang/glog" ) // The namespace under which crio aliases are unique. const CrioNamespace = "crio" // Regexp that identifies CRI-O cgroups var crioCgroupRegexp = regexp.MustCompile(`([a-z0-9]{64})`) type storageDriver string const ( // TODO add full set of supported drivers in future.. overlayStorageDriver storageDriver = "overlay" overlay2StorageDriver storageDriver = "overlay2" ) type crioFactory struct { machineInfoFactory info.MachineInfoFactory storageDriver storageDriver storageDir string // Information about the mounted cgroup subsystems. cgroupSubsystems libcontainer.CgroupSubsystems // Information about mounted filesystems. fsInfo fs.FsInfo ignoreMetrics container.MetricSet client crioClient } func (self *crioFactory) String() string { return CrioNamespace } func (self *crioFactory) NewContainerHandler(name string, inHostNamespace bool) (handler container.ContainerHandler, err error) { client, err := Client() if err != nil { return } // TODO are there any env vars we need to white list, if so, do it here... metadataEnvs := []string{} handler, err = newCrioContainerHandler( client, name, self.machineInfoFactory, self.fsInfo, self.storageDriver, self.storageDir, &self.cgroupSubsystems, inHostNamespace, metadataEnvs, self.ignoreMetrics, ) return } // Returns the CRIO ID from the full container name. func ContainerNameToCrioId(name string) string { id := path.Base(name) if matches := crioCgroupRegexp.FindStringSubmatch(id); matches != nil { return matches[1] } return id } // isContainerName returns true if the cgroup with associated name // corresponds to a crio container. func isContainerName(name string) bool { // always ignore .mount cgroup even if associated with crio and delegate to systemd if strings.HasSuffix(name, ".mount") { return false } return crioCgroupRegexp.MatchString(path.Base(name)) } // crio handles all containers under /crio func (self *crioFactory) CanHandleAndAccept(name string) (bool, bool, error) { if strings.HasPrefix(path.Base(name), "crio-conmon") { // TODO(runcom): should we include crio-conmon cgroups? return false, false, nil } if !strings.HasPrefix(path.Base(name), CrioNamespace) { return false, false, nil } // if the container is not associated with CRI-O, we can't handle it or accept it. if !isContainerName(name) { return false, false, nil } return true, true, nil } func (self *crioFactory) DebugInfo() map[string][]string { return map[string][]string{} } var ( // TODO(runcom): handle versioning in CRI-O version_regexp_string = `(\d+)\.(\d+)\.(\d+)` version_re = regexp.MustCompile(version_regexp_string) apiversion_regexp_string = `(\d+)\.(\d+)` apiversion_re = regexp.MustCompile(apiversion_regexp_string) ) // Register root container before running this function! func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error { client, err := Client() if err != nil { return err } info, err := client.Info() if err != nil { return err } // TODO determine crio version so we can work differently w/ future versions if needed cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() if err != nil { return fmt.Errorf("failed to get cgroup subsystems: %v", err) } glog.Infof("Registering CRI-O factory") f := &crioFactory{ client: client, cgroupSubsystems: cgroupSubsystems, fsInfo: fsInfo, machineInfoFactory: factory, storageDriver: storageDriver(info.StorageDriver), storageDir: info.StorageRoot, ignoreMetrics: ignoreMetrics, } container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw}) return nil } cadvisor-0.27.1/container/crio/factory_test.go000066400000000000000000000035771315410276000214010ustar00rootroot00000000000000// Copyright 2017 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 crio import ( "testing" containerlibcontainer "github.com/google/cadvisor/container/libcontainer" "github.com/stretchr/testify/assert" ) func TestCanHandleAndAccept(t *testing.T) { as := assert.New(t) f := &crioFactory{ client: nil, cgroupSubsystems: containerlibcontainer.CgroupSubsystems{}, fsInfo: nil, machineInfoFactory: nil, storageDriver: "", storageDir: "", ignoreMetrics: nil, } for k, v := range map[string]bool{ "/kubepods/pod068e8fa0-9213-11e7-a01f-507b9d4141fa/crio-81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f": true, "/kubepods/pod068e8fa0-9213-11e7-a01f-507b9d4141fa/crio-81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f.mount": false, "/kubepods/pod068e8fa0-9213-11e7-a01f-507b9d4141fa/crio-conmon-81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f": false, "/kubepods/pod068e8fa0-9213-11e7-a01f-507b9d4141fa/no-crio-conmon-81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f": false, "/kubepods/pod068e8fa0-9213-11e7-a01f-507b9d4141fa/crio-990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75": false, } { b1, b2, err := f.CanHandleAndAccept(k) as.Nil(err) as.Equal(b1, v) as.Equal(b2, v) } } cadvisor-0.27.1/container/crio/handler.go000066400000000000000000000227421315410276000203030ustar00rootroot00000000000000// Copyright 2017 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 CRI-O containers. package crio import ( "fmt" "path" "strconv" "strings" "time" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/common" containerlibcontainer "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/opencontainers/runc/libcontainer/cgroups" cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs" ) type crioContainerHandler struct { name string id string aliases []string machineInfoFactory info.MachineInfoFactory // Absolute path to the cgroup hierarchies of this container. // (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test") cgroupPaths map[string]string // Manager of this container's cgroups. cgroupManager cgroups.Manager // the CRI-O storage driver storageDriver storageDriver fsInfo fs.FsInfo rootfsStorageDir string // Time at which this container was created. creationTime time.Time // Metadata associated with the container. labels map[string]string envs map[string]string // TODO // crio version handling... // The container PID used to switch namespaces as required pid int // Image name used for this container. image string // The host root FS to read rootFs string // The network mode of the container // TODO // Filesystem handler. fsHandler common.FsHandler // The IP address of the container ipAddress string ignoreMetrics container.MetricSet // container restart count restartCount int } var _ container.ContainerHandler = &crioContainerHandler{} // newCrioContainerHandler returns a new container.ContainerHandler func newCrioContainerHandler( client crioClient, name string, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, storageDriver storageDriver, storageDir string, cgroupSubsystems *containerlibcontainer.CgroupSubsystems, inHostNamespace bool, metadataEnvs []string, ignoreMetrics container.MetricSet, ) (container.ContainerHandler, error) { // Create the cgroup paths. cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) for key, val := range cgroupSubsystems.MountPoints { cgroupPaths[key] = path.Join(val, name) } // Generate the equivalent cgroup manager for this container. cgroupManager := &cgroupfs.Manager{ Cgroups: &libcontainerconfigs.Cgroup{ Name: name, }, Paths: cgroupPaths, } rootFs := "/" if !inHostNamespace { rootFs = "/rootfs" storageDir = path.Join(rootFs, storageDir) } id := ContainerNameToCrioId(name) cInfo, err := client.ContainerInfo(id) if err != nil { return nil, err } // passed to fs handler below ... // XXX: this is using the full container logpath, as constructed by the CRI // /var/log/pods//container_instance.log // It's not actually a log dir, as the CRI doesn't have per-container dirs // under /var/log/pods// // We can't use /var/log/pods// to count per-container log usage. // We use the container log file directly. storageLogDir := cInfo.LogPath // Determine the rootfs storage dir rootfsStorageDir := cInfo.Root // TODO(runcom): CRI-O doesn't strip /merged but we need to in order to // get device ID from root, otherwise, it's going to error out as overlay // mounts doesn't have fixed dev ids. rootfsStorageDir = strings.TrimSuffix(rootfsStorageDir, "/merged") // TODO: extract object mother method handler := &crioContainerHandler{ id: id, name: name, machineInfoFactory: machineInfoFactory, cgroupPaths: cgroupPaths, cgroupManager: cgroupManager, storageDriver: storageDriver, fsInfo: fsInfo, rootFs: rootFs, rootfsStorageDir: rootfsStorageDir, envs: make(map[string]string), ignoreMetrics: ignoreMetrics, } handler.creationTime = time.Unix(0, cInfo.CreatedTime) handler.pid = cInfo.Pid handler.aliases = append(handler.aliases, cInfo.Name, id) handler.labels = cInfo.Labels handler.image = cInfo.Image // TODO: we wantd to know graph driver DeviceId (dont think this is needed now) // ignore err and get zero as default, this happens with sandboxes, not sure why... // kube isn't sending restart count in labels for sandboxes. restartCount, _ := strconv.Atoi(cInfo.Annotations["io.kubernetes.container.restartCount"]) handler.restartCount = restartCount handler.ipAddress = cInfo.IP // we optionally collect disk usage metrics if !ignoreMetrics.Has(container.DiskUsageMetrics) { handler.fsHandler = common.NewFsHandler(common.DefaultPeriod, rootfsStorageDir, storageLogDir, fsInfo) } // TODO for env vars we wanted to show from container.Config.Env from whitelist //for _, exposedEnv := range metadataEnvs { //glog.Infof("TODO env whitelist: %v", exposedEnv) //} return handler, nil } func (self *crioContainerHandler) Start() { if self.fsHandler != nil { self.fsHandler.Start() } } func (self *crioContainerHandler) Cleanup() { if self.fsHandler != nil { self.fsHandler.Stop() } } func (self *crioContainerHandler) ContainerReference() (info.ContainerReference, error) { return info.ContainerReference{ Id: self.id, Name: self.name, Aliases: self.aliases, Namespace: CrioNamespace, Labels: self.labels, }, nil } func (self *crioContainerHandler) needNet() bool { if !self.ignoreMetrics.Has(container.NetworkUsageMetrics) { return self.labels["io.kubernetes.container.name"] == "POD" } return false } func (self *crioContainerHandler) GetSpec() (info.ContainerSpec, error) { hasFilesystem := !self.ignoreMetrics.Has(container.DiskUsageMetrics) spec, err := common.GetSpec(self.cgroupPaths, self.machineInfoFactory, self.needNet(), hasFilesystem) spec.Labels = self.labels // Only adds restartcount label if it's greater than 0 if self.restartCount > 0 { spec.Labels["restartcount"] = strconv.Itoa(self.restartCount) } spec.Envs = self.envs spec.Image = self.image return spec, err } func (self *crioContainerHandler) getFsStats(stats *info.ContainerStats) error { mi, err := self.machineInfoFactory.GetMachineInfo() if err != nil { return err } if !self.ignoreMetrics.Has(container.DiskIOMetrics) { common.AssignDeviceNamesToDiskStats((*common.MachineInfoNamer)(mi), &stats.DiskIo) } if self.ignoreMetrics.Has(container.DiskUsageMetrics) { return nil } var device string switch self.storageDriver { case overlay2StorageDriver, overlayStorageDriver: deviceInfo, err := self.fsInfo.GetDirFsDevice(self.rootfsStorageDir) if err != nil { return fmt.Errorf("unable to determine device info for dir: %v: %v", self.rootfsStorageDir, err) } device = deviceInfo.Device default: return nil } var ( limit uint64 fsType string ) // crio does not impose any filesystem limits for containers. So use capacity as limit. for _, fs := range mi.Filesystems { if fs.Device == device { limit = fs.Capacity fsType = fs.Type break } } fsStat := info.FsStats{Device: device, Type: fsType, Limit: limit} usage := self.fsHandler.Usage() fsStat.BaseUsage = usage.BaseUsageBytes fsStat.Usage = usage.TotalUsageBytes fsStat.Inodes = usage.InodeUsage stats.Filesystem = append(stats.Filesystem, fsStat) return nil } func (self *crioContainerHandler) GetStats() (*info.ContainerStats, error) { stats, err := containerlibcontainer.GetStats(self.cgroupManager, self.rootFs, self.pid, self.ignoreMetrics) if err != nil { return stats, err } // Clean up stats for containers that don't have their own network - this // includes containers running in Kubernetes pods that use the network of the // infrastructure container. This stops metrics being reported multiple times // for each container in a pod. if !self.needNet() { stats.Network = info.NetworkStats{} } // Get filesystem stats. err = self.getFsStats(stats) if err != nil { return stats, err } return stats, nil } func (self *crioContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { // No-op for Docker driver. return []info.ContainerReference{}, nil } func (self *crioContainerHandler) GetCgroupPath(resource string) (string, error) { path, ok := self.cgroupPaths[resource] if !ok { return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, self.name) } return path, nil } func (self *crioContainerHandler) GetContainerLabels() map[string]string { return self.labels } func (self *crioContainerHandler) GetContainerIPAddress() string { return self.ipAddress } func (self *crioContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { return containerlibcontainer.GetProcesses(self.cgroupManager) } func (self *crioContainerHandler) Exists() bool { return common.CgroupExists(self.cgroupPaths) } func (self *crioContainerHandler) Type() container.ContainerType { return container.ContainerTypeCrio } cadvisor-0.27.1/container/crio/handler_test.go000066400000000000000000000067721315410276000213470ustar00rootroot00000000000000// Copyright 2017 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 crio import ( "fmt" "testing" "github.com/google/cadvisor/container" containerlibcontainer "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/assert" ) func TestHandler(t *testing.T) { as := assert.New(t) type testCase struct { client crioClient name string machineInfoFactory info.MachineInfoFactory fsInfo fs.FsInfo storageDriver storageDriver storageDir string cgroupSubsystems *containerlibcontainer.CgroupSubsystems inHostNamespace bool metadataEnvs []string ignoreMetrics container.MetricSet hasErr bool errContains string checkReference *info.ContainerReference } for _, ts := range []testCase{ { mockCrioClient(Info{}, nil, fmt.Errorf("no client returned")), "/kubepods/pod068e8fa0-9213-11e7-a01f-507b9d4141fa/crio-81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f", nil, nil, "", "", &containerlibcontainer.CgroupSubsystems{}, false, nil, nil, true, "no client returned", nil, }, { mockCrioClient(Info{}, nil, nil), "/kubepods/pod068e8fa0-9213-11e7-a01f-507b9d4141fa/crio-81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f", nil, nil, "", "", &containerlibcontainer.CgroupSubsystems{}, false, nil, nil, true, "no container with id 81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f", nil, }, { mockCrioClient( Info{}, map[string]*ContainerInfo{"81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f": {Name: "test", Labels: map[string]string{"io.kubernetes.container.name": "POD"}}}, nil, ), "/kubepods/pod068e8fa0-9213-11e7-a01f-507b9d4141fa/crio-81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f", nil, nil, "", "", &containerlibcontainer.CgroupSubsystems{}, false, nil, nil, false, "", &info.ContainerReference{ Id: "81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f", Name: "/kubepods/pod068e8fa0-9213-11e7-a01f-507b9d4141fa/crio-81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f", Aliases: []string{"test", "81e5c2990803c383229c9680ce964738d5e566d97f5bd436ac34808d2ec75d5f"}, Namespace: CrioNamespace, Labels: map[string]string{"io.kubernetes.container.name": "POD"}, }, }, } { handler, err := newCrioContainerHandler(ts.client, ts.name, ts.machineInfoFactory, ts.fsInfo, ts.storageDriver, ts.storageDir, ts.cgroupSubsystems, ts.inHostNamespace, ts.metadataEnvs, ts.ignoreMetrics) if ts.hasErr { as.NotNil(err) if ts.errContains != "" { as.Contains(err.Error(), ts.errContains) } } if ts.checkReference != nil { cr, err := handler.ContainerReference() as.Nil(err) as.Equal(*ts.checkReference, cr) } } } cadvisor-0.27.1/container/docker/000077500000000000000000000000001315410276000166435ustar00rootroot00000000000000cadvisor-0.27.1/container/docker/client.go000066400000000000000000000032031315410276000204460ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Handler for /validate content. // Validates cadvisor dependencies - kernel, os, docker setup. package docker import ( "net/http" "sync" dclient "github.com/docker/engine-api/client" "github.com/docker/go-connections/tlsconfig" ) var ( dockerClient *dclient.Client dockerClientErr error dockerClientOnce sync.Once ) // Client creates a Docker API client based on the given Docker flags func Client() (*dclient.Client, error) { dockerClientOnce.Do(func() { var client *http.Client if *ArgDockerTLS { client = &http.Client{} options := tlsconfig.Options{ CAFile: *ArgDockerCA, CertFile: *ArgDockerCert, KeyFile: *ArgDockerKey, InsecureSkipVerify: false, } tlsc, err := tlsconfig.Client(options) if err != nil { dockerClientErr = err return } client.Transport = &http.Transport{ TLSClientConfig: tlsc, } } dockerClient, dockerClientErr = dclient.NewClient(*ArgDockerEndpoint, "", client, nil) }) return dockerClient, dockerClientErr } cadvisor-0.27.1/container/docker/docker.go000066400000000000000000000125241315410276000204450ustar00rootroot00000000000000// Copyright 2016 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. // Provides global docker information. package docker import ( "fmt" "regexp" "strconv" "strings" dockertypes "github.com/docker/engine-api/types" "golang.org/x/net/context" "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/machine" ) func Status() (v1.DockerStatus, error) { client, err := Client() if err != nil { return v1.DockerStatus{}, fmt.Errorf("unable to communicate with docker daemon: %v", err) } dockerInfo, err := client.Info(context.Background()) if err != nil { return v1.DockerStatus{}, err } return StatusFromDockerInfo(dockerInfo), nil } func StatusFromDockerInfo(dockerInfo dockertypes.Info) v1.DockerStatus { out := v1.DockerStatus{} out.Version = VersionString() out.APIVersion = APIVersionString() out.KernelVersion = machine.KernelVersion() out.OS = dockerInfo.OperatingSystem out.Hostname = dockerInfo.Name out.RootDir = dockerInfo.DockerRootDir out.Driver = dockerInfo.Driver out.ExecDriver = dockerInfo.ExecutionDriver out.NumImages = dockerInfo.Images out.NumContainers = dockerInfo.Containers out.DriverStatus = make(map[string]string, len(dockerInfo.DriverStatus)) for _, v := range dockerInfo.DriverStatus { out.DriverStatus[v[0]] = v[1] } return out } func Images() ([]v1.DockerImage, error) { client, err := Client() if err != nil { return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err) } images, err := client.ImageList(context.Background(), dockertypes.ImageListOptions{All: false}) if err != nil { return nil, err } out := []v1.DockerImage{} const unknownTag = ":" for _, image := range images { if len(image.RepoTags) == 1 && image.RepoTags[0] == unknownTag { // images with repo or tags are uninteresting. continue } di := v1.DockerImage{ ID: image.ID, RepoTags: image.RepoTags, Created: image.Created, VirtualSize: image.VirtualSize, Size: image.Size, } out = append(out, di) } return out, nil } // Checks whether the dockerInfo reflects a valid docker setup, and returns it if it does, or an // error otherwise. func ValidateInfo() (*dockertypes.Info, error) { client, err := Client() if err != nil { return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err) } dockerInfo, err := client.Info(context.Background()) if err != nil { return nil, fmt.Errorf("failed to detect Docker info: %v", err) } // Fall back to version API if ServerVersion is not set in info. if dockerInfo.ServerVersion == "" { version, err := client.ServerVersion(context.Background()) if err != nil { return nil, fmt.Errorf("unable to get docker version: %v", err) } dockerInfo.ServerVersion = version.Version } version, err := parseVersion(dockerInfo.ServerVersion, version_re, 3) if err != nil { return nil, err } if version[0] < 1 { return nil, fmt.Errorf("cAdvisor requires docker version %v or above but we have found version %v reported as %q", []int{1, 0, 0}, version, dockerInfo.ServerVersion) } // Check that the libcontainer execdriver is used if the version is < 1.11 // (execution drivers are no longer supported as of 1.11). if version[0] <= 1 && version[1] <= 10 && !strings.HasPrefix(dockerInfo.ExecutionDriver, "native") { return nil, fmt.Errorf("docker found, but not using native exec driver") } if dockerInfo.Driver == "" { return nil, fmt.Errorf("failed to find docker storage driver") } return &dockerInfo, nil } func Version() ([]int, error) { return parseVersion(VersionString(), version_re, 3) } func APIVersion() ([]int, error) { return parseVersion(APIVersionString(), apiversion_re, 2) } func VersionString() string { docker_version := "Unknown" client, err := Client() if err == nil { version, err := client.ServerVersion(context.Background()) if err == nil { docker_version = version.Version } } return docker_version } func APIVersionString() string { docker_api_version := "Unknown" client, err := Client() if err == nil { version, err := client.ServerVersion(context.Background()) if err == nil { docker_api_version = version.APIVersion } } return docker_api_version } func parseVersion(version_string string, regex *regexp.Regexp, length int) ([]int, error) { matches := regex.FindAllStringSubmatch(version_string, -1) if len(matches) != 1 { return nil, fmt.Errorf("version string \"%v\" doesn't match expected regular expression: \"%v\"", version_string, regex.String()) } version_string_array := matches[0][1:] version_array := make([]int, length) for index, version_str := range version_string_array { version, err := strconv.Atoi(version_str) if err != nil { return nil, fmt.Errorf("error while parsing \"%v\" in \"%v\"", version_str, version_string) } version_array[index] = version } return version_array, nil } cadvisor-0.27.1/container/docker/docker_test.go000066400000000000000000000033051315410276000215010ustar00rootroot00000000000000// Copyright 2017 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 ( "reflect" "regexp" "testing" ) func TestParseDockerAPIVersion(t *testing.T) { tests := []struct { version string regex *regexp.Regexp length int expected []int expectedError string }{ {"17.03.0", version_re, 3, []int{17, 03, 0}, ""}, {"17.a3.0", version_re, 3, []int{}, `version string "17.a3.0" doesn't match expected regular expression: "(\d+)\.(\d+)\.(\d+)"`}, {"1.20", apiversion_re, 2, []int{1, 20}, ""}, {"1.a", apiversion_re, 2, []int{}, `version string "1.a" doesn't match expected regular expression: "(\d+)\.(\d+)"`}, } for _, test := range tests { actual, err := parseVersion(test.version, test.regex, test.length) if err != nil { if len(test.expectedError) == 0 { t.Errorf("%s: expected no error, got %v", test.version, err) } else if err.Error() != test.expectedError { t.Errorf("%s: expected error %v, got %v", test.version, test.expectedError, err) } } else { if !reflect.DeepEqual(actual, test.expected) { t.Errorf("%s: expected array %v, got %v", test.version, test.expected, actual) } } } } cadvisor-0.27.1/container/docker/factory.go000066400000000000000000000255651315410276000206560ustar00rootroot00000000000000// 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" "path" "regexp" "strconv" "strings" "sync" "github.com/blang/semver" dockertypes "github.com/docker/engine-api/types" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/devicemapper" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/machine" "github.com/google/cadvisor/manager/watcher" dockerutil "github.com/google/cadvisor/utils/docker" "github.com/google/cadvisor/zfs" docker "github.com/docker/engine-api/client" "github.com/golang/glog" "golang.org/x/net/context" ) var ArgDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "docker endpoint") var ArgDockerTLS = flag.Bool("docker-tls", false, "use TLS to connect to docker") var ArgDockerCert = flag.String("docker-tls-cert", "cert.pem", "path to client certificate") var ArgDockerKey = flag.String("docker-tls-key", "key.pem", "path to private key") var ArgDockerCA = flag.String("docker-tls-ca", "ca.pem", "path to trusted CA") // The namespace under which Docker aliases are unique. const DockerNamespace = "docker" // Regexp that identifies docker cgroups, containers started with // --cgroup-parent have another prefix than 'docker' var dockerCgroupRegexp = regexp.MustCompile(`([a-z0-9]{64})`) var dockerEnvWhitelist = flag.String("docker_env_metadata_whitelist", "", "a comma-separated list of environment variable keys that needs to be collected for docker containers") var ( // Basepath to all container specific information that libcontainer stores. dockerRootDir string dockerRootDirFlag = flag.String("docker_root", "/var/lib/docker", "DEPRECATED: docker root is read from docker info (this is a fallback, default: /var/lib/docker)") dockerRootDirOnce sync.Once // flag that controls globally disabling thin_ls pending future enhancements. // in production, it has been found that thin_ls makes excessive use of iops. // in an iops restricted environment, usage of thin_ls must be controlled via blkio. // pending that enhancement, disable its usage. disableThinLs = true ) func RootDir() string { dockerRootDirOnce.Do(func() { status, err := Status() if err == nil && status.RootDir != "" { dockerRootDir = status.RootDir } else { dockerRootDir = *dockerRootDirFlag } }) return dockerRootDir } type storageDriver string const ( devicemapperStorageDriver storageDriver = "devicemapper" aufsStorageDriver storageDriver = "aufs" overlayStorageDriver storageDriver = "overlay" overlay2StorageDriver storageDriver = "overlay2" zfsStorageDriver storageDriver = "zfs" ) type dockerFactory struct { machineInfoFactory info.MachineInfoFactory storageDriver storageDriver storageDir string client *docker.Client // Information about the mounted cgroup subsystems. cgroupSubsystems libcontainer.CgroupSubsystems // Information about mounted filesystems. fsInfo fs.FsInfo dockerVersion []int dockerAPIVersion []int ignoreMetrics container.MetricSet thinPoolName string thinPoolWatcher *devicemapper.ThinPoolWatcher zfsWatcher *zfs.ZfsWatcher } func (self *dockerFactory) String() string { return DockerNamespace } func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool) (handler container.ContainerHandler, err error) { client, err := Client() if err != nil { return } metadataEnvs := strings.Split(*dockerEnvWhitelist, ",") handler, err = newDockerContainerHandler( client, name, self.machineInfoFactory, self.fsInfo, self.storageDriver, self.storageDir, &self.cgroupSubsystems, inHostNamespace, metadataEnvs, self.dockerVersion, self.ignoreMetrics, self.thinPoolName, self.thinPoolWatcher, self.zfsWatcher, ) return } // Returns the Docker ID from the full container name. func ContainerNameToDockerId(name string) string { id := path.Base(name) if matches := dockerCgroupRegexp.FindStringSubmatch(id); matches != nil { return matches[1] } return id } // isContainerName returns true if the cgroup with associated name // corresponds to a docker container. func isContainerName(name string) bool { // always ignore .mount cgroup even if associated with docker and delegate to systemd if strings.HasSuffix(name, ".mount") { return false } return dockerCgroupRegexp.MatchString(path.Base(name)) } // Docker handles all containers under /docker func (self *dockerFactory) CanHandleAndAccept(name string) (bool, bool, error) { // if the container is not associated with docker, we can't handle it or accept it. if !isContainerName(name) { return false, false, nil } // Check if the container is known to docker and it is active. id := ContainerNameToDockerId(name) // We assume that if Inspect fails then the container is not known to docker. ctnr, err := self.client.ContainerInspect(context.Background(), id) if err != nil || !ctnr.State.Running { return false, true, fmt.Errorf("error inspecting container: %v", err) } return true, true, nil } func (self *dockerFactory) DebugInfo() map[string][]string { return map[string][]string{} } var ( version_regexp_string = `(\d+)\.(\d+)\.(\d+)` version_re = regexp.MustCompile(version_regexp_string) apiversion_regexp_string = `(\d+)\.(\d+)` apiversion_re = regexp.MustCompile(apiversion_regexp_string) ) func startThinPoolWatcher(dockerInfo *dockertypes.Info) (*devicemapper.ThinPoolWatcher, error) { _, err := devicemapper.ThinLsBinaryPresent() if err != nil { return nil, err } if err := ensureThinLsKernelVersion(machine.KernelVersion()); err != nil { return nil, err } if disableThinLs { return nil, fmt.Errorf("usage of thin_ls is disabled to preserve iops") } dockerThinPoolName, err := dockerutil.DockerThinPoolName(*dockerInfo) if err != nil { return nil, err } dockerMetadataDevice, err := dockerutil.DockerMetadataDevice(*dockerInfo) if err != nil { return nil, err } thinPoolWatcher, err := devicemapper.NewThinPoolWatcher(dockerThinPoolName, dockerMetadataDevice) if err != nil { return nil, err } go thinPoolWatcher.Start() return thinPoolWatcher, nil } func startZfsWatcher(dockerInfo *dockertypes.Info) (*zfs.ZfsWatcher, error) { filesystem, err := dockerutil.DockerZfsFilesystem(*dockerInfo) if err != nil { return nil, err } zfsWatcher, err := zfs.NewZfsWatcher(filesystem) if err != nil { return nil, err } go zfsWatcher.Start() return zfsWatcher, nil } func ensureThinLsKernelVersion(kernelVersion string) error { // kernel 4.4.0 has the proper bug fixes to allow thin_ls to work without corrupting the thin pool minKernelVersion := semver.MustParse("4.4.0") // RHEL 7 kernel 3.10.0 release >= 366 has the proper bug fixes backported from 4.4.0 to allow // thin_ls to work without corrupting the thin pool minRhel7KernelVersion := semver.MustParse("3.10.0") matches := version_re.FindStringSubmatch(kernelVersion) if len(matches) < 4 { return fmt.Errorf("error parsing kernel version: %q is not a semver", kernelVersion) } sem, err := semver.Make(matches[0]) if err != nil { return err } if sem.GTE(minKernelVersion) { // kernel 4.4+ - good return nil } // Certain RHEL/Centos 7.x kernels have a backport to fix the corruption bug if !strings.Contains(kernelVersion, ".el7") { // not a RHEL 7.x kernel - won't work return fmt.Errorf("kernel version 4.4.0 or later is required to use thin_ls - you have %q", kernelVersion) } // RHEL/Centos 7.x from here on if sem.Major != 3 { // only 3.x kernels *may* work correctly return fmt.Errorf("RHEL/Centos 7.x kernel version 3.10.0-366 or later is required to use thin_ls - you have %q", kernelVersion) } if sem.GT(minRhel7KernelVersion) { // 3.10.1+ - good return nil } if sem.EQ(minRhel7KernelVersion) { // need to check release releaseRE := regexp.MustCompile(`^[^-]+-([0-9]+)\.`) releaseMatches := releaseRE.FindStringSubmatch(kernelVersion) if len(releaseMatches) != 2 { return fmt.Errorf("unable to determine RHEL/Centos 7.x kernel release from %q", kernelVersion) } release, err := strconv.Atoi(releaseMatches[1]) if err != nil { return fmt.Errorf("error parsing release %q: %v", releaseMatches[1], err) } if release >= 366 { return nil } } return fmt.Errorf("RHEL/Centos 7.x kernel version 3.10.0-366 or later is required to use thin_ls - you have %q", kernelVersion) } // Register root container before running this function! func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error { client, err := Client() if err != nil { return fmt.Errorf("unable to communicate with docker daemon: %v", err) } dockerInfo, err := ValidateInfo() if err != nil { return fmt.Errorf("failed to validate Docker info: %v", err) } // Version already validated above, assume no error here. dockerVersion, _ := parseVersion(dockerInfo.ServerVersion, version_re, 3) dockerAPIVersion, _ := APIVersion() cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() if err != nil { return fmt.Errorf("failed to get cgroup subsystems: %v", err) } var ( thinPoolWatcher *devicemapper.ThinPoolWatcher thinPoolName string ) if storageDriver(dockerInfo.Driver) == devicemapperStorageDriver { thinPoolWatcher, err = startThinPoolWatcher(dockerInfo) if err != nil { glog.Errorf("devicemapper filesystem stats will not be reported: %v", err) } status := StatusFromDockerInfo(*dockerInfo) thinPoolName = status.DriverStatus[dockerutil.DriverStatusPoolName] } var zfsWatcher *zfs.ZfsWatcher if storageDriver(dockerInfo.Driver) == zfsStorageDriver { zfsWatcher, err = startZfsWatcher(dockerInfo) if err != nil { glog.Errorf("zfs filesystem stats will not be reported: %v", err) } } glog.Infof("Registering Docker factory") f := &dockerFactory{ cgroupSubsystems: cgroupSubsystems, client: client, dockerVersion: dockerVersion, dockerAPIVersion: dockerAPIVersion, fsInfo: fsInfo, machineInfoFactory: factory, storageDriver: storageDriver(dockerInfo.Driver), storageDir: RootDir(), ignoreMetrics: ignoreMetrics, thinPoolName: thinPoolName, thinPoolWatcher: thinPoolWatcher, zfsWatcher: zfsWatcher, } container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw}) return nil } cadvisor-0.27.1/container/docker/factory_test.go000066400000000000000000000051671315410276000217110ustar00rootroot00000000000000// Copyright 2016 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 "testing" func TestEnsureThinLsKernelVersion(t *testing.T) { tests := []struct { version string expectedError string }{ {"4.4.0-31-generic", ""}, {"4.4.1", ""}, {"4.6.4-301.fc24.x86_64", ""}, {"3.10.0-327.22.2.el7.x86_64", `RHEL/Centos 7.x kernel version 3.10.0-366 or later is required to use thin_ls - you have "3.10.0-327.22.2.el7.x86_64"`}, {"3.10.0-366.el7.x86_64", ""}, {"3.10.0-366.el7_3.x86_64", ""}, {"3.10.0.el7.abc", `unable to determine RHEL/Centos 7.x kernel release from "3.10.0.el7.abc"`}, {"3.10.0-abc.el7.blarg", `unable to determine RHEL/Centos 7.x kernel release from "3.10.0-abc.el7.blarg"`}, {"3.10.0-367.el7.x86_64", ""}, {"3.10.0-366.x86_64", `kernel version 4.4.0 or later is required to use thin_ls - you have "3.10.0-366.x86_64"`}, {"3.10.1-1.el7.x86_64", ""}, {"2.0.36", `kernel version 4.4.0 or later is required to use thin_ls - you have "2.0.36"`}, {"2.1", `error parsing kernel version: "2.1" is not a semver`}, } for _, test := range tests { err := ensureThinLsKernelVersion(test.version) if err != nil { if len(test.expectedError) == 0 { t.Errorf("%s: expected no error, got %v", test.version, err) } else if err.Error() != test.expectedError { t.Errorf("%s: expected error %v, got %v", test.version, test.expectedError, err) } } else if err == nil && len(test.expectedError) > 0 { t.Errorf("%s: expected error %v", test.version, test.expectedError) } } } func TestIsContainerName(t *testing.T) { tests := []struct { name string expected bool }{ { name: "/system.slice/var-lib-docker-overlay-9f086b233ab7c786bf8b40b164680b658a8f00e94323868e288d6ce20bc92193-merged.mount", expected: false, }, { name: "/system.slice/docker-72e5a5ff5eef3c4222a6551b992b9360a99122f77d2229783f0ee0946dfd800e.scope", expected: true, }, } for _, test := range tests { if actual := isContainerName(test.name); actual != test.expected { t.Errorf("%s: expected: %v, actual: %v", test.name, test.expected, actual) } } } cadvisor-0.27.1/container/docker/handler.go000066400000000000000000000365761315410276000206300ustar00rootroot00000000000000// 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 Docker containers. package docker import ( "fmt" "io/ioutil" "path" "strconv" "strings" "time" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/common" containerlibcontainer "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/devicemapper" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" dockerutil "github.com/google/cadvisor/utils/docker" "github.com/google/cadvisor/zfs" docker "github.com/docker/engine-api/client" dockercontainer "github.com/docker/engine-api/types/container" "github.com/golang/glog" "github.com/opencontainers/runc/libcontainer/cgroups" cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs" "golang.org/x/net/context" ) const ( // The read write layers exist here. aufsRWLayer = "diff" // Path to the directory where docker stores log files if the json logging driver is enabled. pathToContainersDir = "containers" ) type dockerContainerHandler struct { client *docker.Client name string id string aliases []string machineInfoFactory info.MachineInfoFactory // Absolute path to the cgroup hierarchies of this container. // (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test") cgroupPaths map[string]string // Manager of this container's cgroups. cgroupManager cgroups.Manager // the docker storage driver storageDriver storageDriver fsInfo fs.FsInfo rootfsStorageDir string // devicemapper state // the devicemapper poolname poolName string // the devicemapper device id for the container deviceID string // zfs Filesystem zfsFilesystem string // zfsParent is the parent for docker zfs zfsParent string // Time at which this container was created. creationTime time.Time // Metadata associated with the container. labels map[string]string envs map[string]string // The container PID used to switch namespaces as required pid int // Image name used for this container. image string // The host root FS to read rootFs string // The network mode of the container networkMode dockercontainer.NetworkMode // Filesystem handler. fsHandler common.FsHandler // The IP address of the container ipAddress string ignoreMetrics container.MetricSet // thin pool watcher thinPoolWatcher *devicemapper.ThinPoolWatcher // zfs watcher zfsWatcher *zfs.ZfsWatcher // container restart count restartCount int } var _ container.ContainerHandler = &dockerContainerHandler{} func getRwLayerID(containerID, storageDir string, sd storageDriver, dockerVersion []int) (string, error) { const ( // Docker version >=1.10.0 have a randomized ID for the root fs of a container. randomizedRWLayerMinorVersion = 10 rwLayerIDFile = "mount-id" ) if (dockerVersion[0] <= 1) && (dockerVersion[1] < randomizedRWLayerMinorVersion) { return containerID, nil } bytes, err := ioutil.ReadFile(path.Join(storageDir, "image", string(sd), "layerdb", "mounts", containerID, rwLayerIDFile)) if err != nil { return "", fmt.Errorf("failed to identify the read-write layer ID for container %q. - %v", containerID, err) } return string(bytes), err } // newDockerContainerHandler returns a new container.ContainerHandler func newDockerContainerHandler( client *docker.Client, name string, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, storageDriver storageDriver, storageDir string, cgroupSubsystems *containerlibcontainer.CgroupSubsystems, inHostNamespace bool, metadataEnvs []string, dockerVersion []int, ignoreMetrics container.MetricSet, thinPoolName string, thinPoolWatcher *devicemapper.ThinPoolWatcher, zfsWatcher *zfs.ZfsWatcher, ) (container.ContainerHandler, error) { // Create the cgroup paths. cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) for key, val := range cgroupSubsystems.MountPoints { cgroupPaths[key] = path.Join(val, name) } // Generate the equivalent cgroup manager for this container. cgroupManager := &cgroupfs.Manager{ Cgroups: &libcontainerconfigs.Cgroup{ Name: name, }, Paths: cgroupPaths, } rootFs := "/" if !inHostNamespace { rootFs = "/rootfs" storageDir = path.Join(rootFs, storageDir) } id := ContainerNameToDockerId(name) // Add the Containers dir where the log files are stored. // FIXME: Give `otherStorageDir` a more descriptive name. otherStorageDir := path.Join(storageDir, pathToContainersDir, id) rwLayerID, err := getRwLayerID(id, storageDir, storageDriver, dockerVersion) if err != nil { return nil, err } // Determine the rootfs storage dir OR the pool name to determine the device. // For devicemapper, we only need the thin pool name, and that is passed in to this call var ( rootfsStorageDir string zfsFilesystem string zfsParent string ) switch storageDriver { case aufsStorageDriver: rootfsStorageDir = path.Join(storageDir, string(aufsStorageDriver), aufsRWLayer, rwLayerID) case overlayStorageDriver, overlay2StorageDriver: rootfsStorageDir = path.Join(storageDir, string(storageDriver), rwLayerID) case zfsStorageDriver: status, err := Status() if err != nil { return nil, fmt.Errorf("unable to determine docker status: %v", err) } zfsParent = status.DriverStatus[dockerutil.DriverStatusParentDataset] zfsFilesystem = path.Join(zfsParent, rwLayerID) } // TODO: extract object mother method handler := &dockerContainerHandler{ id: id, client: client, name: name, machineInfoFactory: machineInfoFactory, cgroupPaths: cgroupPaths, cgroupManager: cgroupManager, storageDriver: storageDriver, fsInfo: fsInfo, rootFs: rootFs, poolName: thinPoolName, zfsFilesystem: zfsFilesystem, rootfsStorageDir: rootfsStorageDir, envs: make(map[string]string), ignoreMetrics: ignoreMetrics, thinPoolWatcher: thinPoolWatcher, zfsWatcher: zfsWatcher, zfsParent: zfsParent, } // We assume that if Inspect fails then the container is not known to docker. ctnr, err := client.ContainerInspect(context.Background(), id) if err != nil { return nil, fmt.Errorf("failed to inspect container %q: %v", id, err) } // Timestamp returned by Docker is in time.RFC3339Nano format. handler.creationTime, err = time.Parse(time.RFC3339Nano, ctnr.Created) if err != nil { // This should not happen, report the error just in case return nil, fmt.Errorf("failed to parse the create timestamp %q for container %q: %v", ctnr.Created, id, err) } handler.pid = ctnr.State.Pid // Add the name and bare ID as aliases of the container. handler.aliases = append(handler.aliases, strings.TrimPrefix(ctnr.Name, "/"), id) handler.labels = ctnr.Config.Labels handler.image = ctnr.Config.Image handler.networkMode = ctnr.HostConfig.NetworkMode handler.deviceID = ctnr.GraphDriver.Data["DeviceId"] handler.restartCount = ctnr.RestartCount // Obtain the IP address for the contianer. // If the NetworkMode starts with 'container:' then we need to use the IP address of the container specified. // This happens in cases such as kubernetes where the containers doesn't have an IP address itself and we need to use the pod's address ipAddress := ctnr.NetworkSettings.IPAddress networkMode := string(ctnr.HostConfig.NetworkMode) if ipAddress == "" && strings.HasPrefix(networkMode, "container:") { containerId := strings.TrimPrefix(networkMode, "container:") c, err := client.ContainerInspect(context.Background(), containerId) if err != nil { return nil, fmt.Errorf("failed to inspect container %q: %v", id, err) } ipAddress = c.NetworkSettings.IPAddress } handler.ipAddress = ipAddress if !ignoreMetrics.Has(container.DiskUsageMetrics) { handler.fsHandler = &dockerFsHandler{ fsHandler: common.NewFsHandler(common.DefaultPeriod, rootfsStorageDir, otherStorageDir, fsInfo), thinPoolWatcher: thinPoolWatcher, zfsWatcher: zfsWatcher, deviceID: handler.deviceID, zfsFilesystem: zfsFilesystem, } } // split env vars to get metadata map. for _, exposedEnv := range metadataEnvs { for _, envVar := range ctnr.Config.Env { if envVar != "" { splits := strings.SplitN(envVar, "=", 2) if len(splits) == 2 && splits[0] == exposedEnv { handler.envs[strings.ToLower(exposedEnv)] = splits[1] } } } } return handler, nil } // dockerFsHandler is a composite FsHandler implementation the incorporates // the common fs handler, a devicemapper ThinPoolWatcher, and a zfsWatcher type dockerFsHandler struct { fsHandler common.FsHandler // thinPoolWatcher is the devicemapper thin pool watcher thinPoolWatcher *devicemapper.ThinPoolWatcher // deviceID is the id of the container's fs device deviceID string // zfsWatcher is the zfs filesystem watcher zfsWatcher *zfs.ZfsWatcher // zfsFilesystem is the docker zfs filesystem zfsFilesystem string } var _ common.FsHandler = &dockerFsHandler{} func (h *dockerFsHandler) Start() { h.fsHandler.Start() } func (h *dockerFsHandler) Stop() { h.fsHandler.Stop() } func (h *dockerFsHandler) Usage() common.FsUsage { usage := h.fsHandler.Usage() // When devicemapper is the storage driver, the base usage of the container comes from the thin pool. // We still need the result of the fsHandler for any extra storage associated with the container. // To correctly factor in the thin pool usage, we should: // * Usage the thin pool usage as the base usage // * Calculate the overall usage by adding the overall usage from the fs handler to the thin pool usage if h.thinPoolWatcher != nil { thinPoolUsage, err := h.thinPoolWatcher.GetUsage(h.deviceID) if err != nil { // TODO: ideally we should keep track of how many times we failed to get the usage for this // device vs how many refreshes of the cache there have been, and display an error e.g. if we've // had at least 1 refresh and we still can't find the device. glog.V(5).Infof("unable to get fs usage from thin pool for device %s: %v", h.deviceID, err) } else { usage.BaseUsageBytes = thinPoolUsage usage.TotalUsageBytes += thinPoolUsage } } if h.zfsWatcher != nil { zfsUsage, err := h.zfsWatcher.GetUsage(h.zfsFilesystem) if err != nil { glog.V(5).Infof("unable to get fs usage from zfs for filesystem %s: %v", h.zfsFilesystem, err) } else { usage.BaseUsageBytes = zfsUsage usage.TotalUsageBytes += zfsUsage } } return usage } func (self *dockerContainerHandler) Start() { if self.fsHandler != nil { self.fsHandler.Start() } } func (self *dockerContainerHandler) Cleanup() { if self.fsHandler != nil { self.fsHandler.Stop() } } func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) { return info.ContainerReference{ Id: self.id, Name: self.name, Aliases: self.aliases, Namespace: DockerNamespace, Labels: self.labels, }, nil } func (self *dockerContainerHandler) needNet() bool { if !self.ignoreMetrics.Has(container.NetworkUsageMetrics) { return !self.networkMode.IsContainer() } return false } func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) { hasFilesystem := !self.ignoreMetrics.Has(container.DiskUsageMetrics) spec, err := common.GetSpec(self.cgroupPaths, self.machineInfoFactory, self.needNet(), hasFilesystem) spec.Labels = self.labels // Only adds restartcount label if it's greater than 0 if self.restartCount > 0 { spec.Labels["restartcount"] = strconv.Itoa(self.restartCount) } spec.Envs = self.envs spec.Image = self.image return spec, err } func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error { mi, err := self.machineInfoFactory.GetMachineInfo() if err != nil { return err } if !self.ignoreMetrics.Has(container.DiskIOMetrics) { common.AssignDeviceNamesToDiskStats((*common.MachineInfoNamer)(mi), &stats.DiskIo) } if self.ignoreMetrics.Has(container.DiskUsageMetrics) { return nil } var device string switch self.storageDriver { case devicemapperStorageDriver: // Device has to be the pool name to correlate with the device name as // set in the machine info filesystems. device = self.poolName case aufsStorageDriver, overlayStorageDriver, overlay2StorageDriver: deviceInfo, err := self.fsInfo.GetDirFsDevice(self.rootfsStorageDir) if err != nil { return fmt.Errorf("unable to determine device info for dir: %v: %v", self.rootfsStorageDir, err) } device = deviceInfo.Device case zfsStorageDriver: device = self.zfsParent default: return nil } var ( limit uint64 fsType string ) // Docker does not impose any filesystem limits for containers. So use capacity as limit. for _, fs := range mi.Filesystems { if fs.Device == device { limit = fs.Capacity fsType = fs.Type break } } fsStat := info.FsStats{Device: device, Type: fsType, Limit: limit} usage := self.fsHandler.Usage() fsStat.BaseUsage = usage.BaseUsageBytes fsStat.Usage = usage.TotalUsageBytes fsStat.Inodes = usage.InodeUsage stats.Filesystem = append(stats.Filesystem, fsStat) return nil } // TODO(vmarmol): Get from libcontainer API instead of cgroup manager when we don't have to support older Dockers. func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error) { stats, err := containerlibcontainer.GetStats(self.cgroupManager, self.rootFs, self.pid, self.ignoreMetrics) if err != nil { return stats, err } // Clean up stats for containers that don't have their own network - this // includes containers running in Kubernetes pods that use the network of the // infrastructure container. This stops metrics being reported multiple times // for each container in a pod. if !self.needNet() { stats.Network = info.NetworkStats{} } // Get filesystem stats. err = self.getFsStats(stats) if err != nil { return stats, err } return stats, nil } func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { // No-op for Docker driver. return []info.ContainerReference{}, nil } func (self *dockerContainerHandler) GetCgroupPath(resource string) (string, error) { path, ok := self.cgroupPaths[resource] if !ok { return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, self.name) } return path, nil } func (self *dockerContainerHandler) GetContainerLabels() map[string]string { return self.labels } func (self *dockerContainerHandler) GetContainerIPAddress() string { return self.ipAddress } func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { return containerlibcontainer.GetProcesses(self.cgroupManager) } func (self *dockerContainerHandler) Exists() bool { return common.CgroupExists(self.cgroupPaths) } func (self *dockerContainerHandler) Type() container.ContainerType { return container.ContainerTypeDocker } cadvisor-0.27.1/container/docker/handler_test.go000066400000000000000000000031041315410276000216440ustar00rootroot00000000000000// 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 Docker containers. package docker import ( "io/ioutil" "os" "path" "testing" "github.com/stretchr/testify/assert" ) func TestStorageDirDetectionWithOldVersions(t *testing.T) { as := assert.New(t) rwLayer, err := getRwLayerID("abcd", "/", aufsStorageDriver, []int{1, 9, 0}) as.Nil(err) as.Equal(rwLayer, "abcd") } func TestStorageDirDetectionWithNewVersions(t *testing.T) { as := assert.New(t) testDir, err := ioutil.TempDir("", "") as.Nil(err) containerID := "abcd" randomizedID := "xyz" randomIDPath := path.Join(testDir, "image/aufs/layerdb/mounts/", containerID) as.Nil(os.MkdirAll(randomIDPath, os.ModePerm)) as.Nil(ioutil.WriteFile(path.Join(randomIDPath, "mount-id"), []byte(randomizedID), os.ModePerm)) rwLayer, err := getRwLayerID(containerID, testDir, "aufs", []int{1, 10, 0}) as.Nil(err) as.Equal(rwLayer, randomizedID) rwLayer, err = getRwLayerID(containerID, testDir, "aufs", []int{1, 10, 0}) as.Nil(err) as.Equal(rwLayer, randomizedID) } cadvisor-0.27.1/container/factory.go000066400000000000000000000105411315410276000173730ustar00rootroot00000000000000// 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" "sync" "github.com/google/cadvisor/manager/watcher" "github.com/golang/glog" ) type ContainerHandlerFactory interface { // Create a new ContainerHandler using this factory. CanHandleAndAccept() must have returned true. NewContainerHandler(name string, inHostNamespace bool) (c ContainerHandler, err error) // Returns whether this factory can handle and accept the specified container. CanHandleAndAccept(name string) (handle bool, accept bool, err error) // Name of the factory. String() string // Returns debugging information. Map of lines per category. DebugInfo() map[string][]string } // MetricKind represents the kind of metrics that cAdvisor exposes. type MetricKind string const ( CpuUsageMetrics MetricKind = "cpu" MemoryUsageMetrics MetricKind = "memory" CpuLoadMetrics MetricKind = "cpuLoad" DiskIOMetrics MetricKind = "diskIO" DiskUsageMetrics MetricKind = "disk" NetworkUsageMetrics MetricKind = "network" NetworkTcpUsageMetrics MetricKind = "tcp" NetworkUdpUsageMetrics MetricKind = "udp" AppMetrics MetricKind = "app" ) func (mk MetricKind) String() string { return string(mk) } type MetricSet map[MetricKind]struct{} func (ms MetricSet) Has(mk MetricKind) bool { _, exists := ms[mk] return exists } func (ms MetricSet) Add(mk MetricKind) { ms[mk] = struct{}{} } // TODO(vmarmol): Consider not making this global. // Global list of factories. var ( factories = map[watcher.ContainerWatchSource][]ContainerHandlerFactory{} factoriesLock sync.RWMutex ) // Register a ContainerHandlerFactory. These should be registered from least general to most general // as they will be asked in order whether they can handle a particular container. func RegisterContainerHandlerFactory(factory ContainerHandlerFactory, watchTypes []watcher.ContainerWatchSource) { factoriesLock.Lock() defer factoriesLock.Unlock() for _, watchType := range watchTypes { factories[watchType] = append(factories[watchType], factory) } } // Returns whether there are any container handler factories registered. func HasFactories() bool { factoriesLock.Lock() defer factoriesLock.Unlock() return len(factories) != 0 } // Create a new ContainerHandler for the specified container. func NewContainerHandler(name string, watchType watcher.ContainerWatchSource, inHostNamespace bool) (ContainerHandler, bool, error) { factoriesLock.RLock() defer factoriesLock.RUnlock() // Create the ContainerHandler with the first factory that supports it. for _, factory := range factories[watchType] { canHandle, canAccept, err := factory.CanHandleAndAccept(name) if err != nil { glog.V(4).Infof("Error trying to work out if we can handle %s: %v", name, err) } if canHandle { if !canAccept { glog.V(3).Infof("Factory %q can handle container %q, but ignoring.", factory, name) return nil, false, nil } glog.V(3).Infof("Using factory %q for container %q", factory, name) handle, err := factory.NewContainerHandler(name, inHostNamespace) return handle, canAccept, err } else { glog.V(4).Infof("Factory %q was unable to handle container %q", factory, name) } } return nil, false, fmt.Errorf("no known factory can handle creation of container") } // Clear the known factories. func ClearContainerHandlerFactories() { factoriesLock.Lock() defer factoriesLock.Unlock() factories = map[watcher.ContainerWatchSource][]ContainerHandlerFactory{} } func DebugInfo() map[string][]string { factoriesLock.RLock() defer factoriesLock.RUnlock() // Get debug information for all factories. out := make(map[string][]string) for _, factoriesSlice := range factories { for _, factory := range factoriesSlice { for k, v := range factory.DebugInfo() { out[k] = v } } } return out } cadvisor-0.27.1/container/factory_test.go000066400000000000000000000120001315410276000204220ustar00rootroot00000000000000// 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_test import ( "testing" "github.com/google/cadvisor/container" containertest "github.com/google/cadvisor/container/testing" "github.com/google/cadvisor/manager/watcher" "github.com/stretchr/testify/mock" ) type mockContainerHandlerFactory struct { mock.Mock Name string CanHandleValue bool CanAcceptValue bool } func (self *mockContainerHandlerFactory) String() string { return self.Name } func (self *mockContainerHandlerFactory) DebugInfo() map[string][]string { return map[string][]string{} } func (self *mockContainerHandlerFactory) CanHandleAndAccept(name string) (bool, bool, error) { return self.CanHandleValue, self.CanAcceptValue, nil } func (self *mockContainerHandlerFactory) NewContainerHandler(name string, isHostNamespace bool) (container.ContainerHandler, error) { args := self.Called(name) return args.Get(0).(container.ContainerHandler), args.Error(1) } const testContainerName = "/test" var mockFactory containertest.FactoryForMockContainerHandler func TestNewContainerHandler_FirstMatches(t *testing.T) { container.ClearContainerHandlerFactories() // Register one allways yes factory. allwaysYes := &mockContainerHandlerFactory{ Name: "yes", CanHandleValue: true, CanAcceptValue: true, } container.RegisterContainerHandlerFactory(allwaysYes, []watcher.ContainerWatchSource{watcher.Raw}) // The yes factory should be asked to create the ContainerHandler. mockContainer, err := mockFactory.NewContainerHandler(testContainerName, true) if err != nil { t.Error(err) } allwaysYes.On("NewContainerHandler", testContainerName).Return(mockContainer, nil) cont, _, err := container.NewContainerHandler(testContainerName, watcher.Raw, true) if err != nil { t.Error(err) } if cont == nil { t.Error("Expected container to not be nil") } } func TestNewContainerHandler_SecondMatches(t *testing.T) { container.ClearContainerHandlerFactories() // Register one allways no and one always yes factory. allwaysNo := &mockContainerHandlerFactory{ Name: "no", CanHandleValue: false, CanAcceptValue: true, } container.RegisterContainerHandlerFactory(allwaysNo, []watcher.ContainerWatchSource{watcher.Raw}) allwaysYes := &mockContainerHandlerFactory{ Name: "yes", CanHandleValue: true, CanAcceptValue: true, } container.RegisterContainerHandlerFactory(allwaysYes, []watcher.ContainerWatchSource{watcher.Raw}) // The yes factory should be asked to create the ContainerHandler. mockContainer, err := mockFactory.NewContainerHandler(testContainerName, true) if err != nil { t.Error(err) } allwaysYes.On("NewContainerHandler", testContainerName).Return(mockContainer, nil) cont, _, err := container.NewContainerHandler(testContainerName, watcher.Raw, true) if err != nil { t.Error(err) } if cont == nil { t.Error("Expected container to not be nil") } } func TestNewContainerHandler_NoneMatch(t *testing.T) { container.ClearContainerHandlerFactories() // Register two allways no factories. allwaysNo1 := &mockContainerHandlerFactory{ Name: "no", CanHandleValue: false, CanAcceptValue: true, } container.RegisterContainerHandlerFactory(allwaysNo1, []watcher.ContainerWatchSource{watcher.Raw}) allwaysNo2 := &mockContainerHandlerFactory{ Name: "no", CanHandleValue: false, CanAcceptValue: true, } container.RegisterContainerHandlerFactory(allwaysNo2, []watcher.ContainerWatchSource{watcher.Raw}) _, _, err := container.NewContainerHandler(testContainerName, watcher.Raw, true) if err == nil { t.Error("Expected NewContainerHandler to fail") } } func TestNewContainerHandler_Accept(t *testing.T) { container.ClearContainerHandlerFactories() // Register handler that can handle the container, but can't accept it. cannotHandle := &mockContainerHandlerFactory{ Name: "no", CanHandleValue: false, CanAcceptValue: true, } container.RegisterContainerHandlerFactory(cannotHandle, []watcher.ContainerWatchSource{watcher.Raw}) cannotAccept := &mockContainerHandlerFactory{ Name: "no", CanHandleValue: true, CanAcceptValue: false, } container.RegisterContainerHandlerFactory(cannotAccept, []watcher.ContainerWatchSource{watcher.Raw}) _, accept, err := container.NewContainerHandler(testContainerName, watcher.Raw, true) if err != nil { t.Error("Expected NewContainerHandler to succeed") } if accept == true { t.Error("Expected NewContainerHandler to ignore the container.") } } cadvisor-0.27.1/container/libcontainer/000077500000000000000000000000001315410276000200455ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/helpers.go000066400000000000000000000367031315410276000220470ustar00rootroot00000000000000// 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 libcontainer import ( "bufio" "fmt" "io" "io/ioutil" "os" "path" "strconv" "strings" "time" "github.com/google/cadvisor/container" info "github.com/google/cadvisor/info/v1" "github.com/golang/glog" "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer/cgroups" ) /* #include */ import "C" type CgroupSubsystems struct { // Cgroup subsystem mounts. // e.g.: "/sys/fs/cgroup/cpu" -> ["cpu", "cpuacct"] Mounts []cgroups.Mount // Cgroup subsystem to their mount location. // e.g.: "cpu" -> "/sys/fs/cgroup/cpu" MountPoints map[string]string } // Get information about the cgroup subsystems. func GetCgroupSubsystems() (CgroupSubsystems, error) { // Get all cgroup mounts. allCgroups, err := cgroups.GetCgroupMounts(true) if err != nil { return CgroupSubsystems{}, err } if len(allCgroups) == 0 { return CgroupSubsystems{}, fmt.Errorf("failed to find cgroup mounts") } // Trim the mounts to only the subsystems we care about. supportedCgroups := make([]cgroups.Mount, 0, len(allCgroups)) mountPoints := make(map[string]string, len(allCgroups)) for _, mount := range allCgroups { for _, subsystem := range mount.Subsystems { if _, ok := supportedSubsystems[subsystem]; ok { supportedCgroups = append(supportedCgroups, mount) mountPoints[subsystem] = mount.Mountpoint } } } return CgroupSubsystems{ Mounts: supportedCgroups, MountPoints: mountPoints, }, nil } // Cgroup subsystems we support listing (should be the minimal set we need stats from). var supportedSubsystems map[string]struct{} = map[string]struct{}{ "cpu": {}, "cpuacct": {}, "memory": {}, "cpuset": {}, "blkio": {}, } // Get cgroup and networking stats of the specified container func GetStats(cgroupManager cgroups.Manager, rootFs string, pid int, ignoreMetrics container.MetricSet) (*info.ContainerStats, error) { cgroupStats, err := cgroupManager.GetStats() if err != nil { return nil, err } libcontainerStats := &libcontainer.Stats{ CgroupStats: cgroupStats, } stats := newContainerStats(libcontainerStats) // If we know the pid then get network stats from /proc//net/dev if pid == 0 { return stats, nil } if !ignoreMetrics.Has(container.NetworkUsageMetrics) { netStats, err := networkStatsFromProc(rootFs, pid) if err != nil { glog.V(4).Infof("Unable to get network stats from pid %d: %v", pid, err) } else { stats.Network.Interfaces = append(stats.Network.Interfaces, netStats...) } } if !ignoreMetrics.Has(container.NetworkTcpUsageMetrics) { t, err := tcpStatsFromProc(rootFs, pid, "net/tcp") if err != nil { glog.V(4).Infof("Unable to get tcp stats from pid %d: %v", pid, err) } else { stats.Network.Tcp = t } t6, err := tcpStatsFromProc(rootFs, pid, "net/tcp6") if err != nil { glog.V(4).Infof("Unable to get tcp6 stats from pid %d: %v", pid, err) } else { stats.Network.Tcp6 = t6 } } if !ignoreMetrics.Has(container.NetworkUdpUsageMetrics) { u, err := udpStatsFromProc(rootFs, pid, "net/udp") if err != nil { glog.V(4).Infof("Unable to get udp stats from pid %d: %v", pid, err) } else { stats.Network.Udp = u } u6, err := udpStatsFromProc(rootFs, pid, "net/udp6") if err != nil { glog.V(4).Infof("Unable to get udp6 stats from pid %d: %v", pid, err) } else { stats.Network.Udp6 = u6 } } // For backwards compatibility. if len(stats.Network.Interfaces) > 0 { stats.Network.InterfaceStats = stats.Network.Interfaces[0] } return stats, nil } func networkStatsFromProc(rootFs string, pid int) ([]info.InterfaceStats, error) { netStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), "/net/dev") ifaceStats, err := scanInterfaceStats(netStatsFile) if err != nil { return []info.InterfaceStats{}, fmt.Errorf("couldn't read network stats: %v", err) } return ifaceStats, nil } var ( ignoredDevicePrefixes = []string{"lo", "veth", "docker"} ) func isIgnoredDevice(ifName string) bool { for _, prefix := range ignoredDevicePrefixes { if strings.HasPrefix(strings.ToLower(ifName), prefix) { return true } } return false } func scanInterfaceStats(netStatsFile string) ([]info.InterfaceStats, error) { file, err := os.Open(netStatsFile) if err != nil { return nil, fmt.Errorf("failure opening %s: %v", netStatsFile, err) } defer file.Close() scanner := bufio.NewScanner(file) // Discard header lines for i := 0; i < 2; i++ { if b := scanner.Scan(); !b { return nil, scanner.Err() } } stats := []info.InterfaceStats{} for scanner.Scan() { line := scanner.Text() line = strings.Replace(line, ":", "", -1) fields := strings.Fields(line) // If the format of the line is invalid then don't trust any of the stats // in this file. if len(fields) != 17 { return nil, fmt.Errorf("invalid interface stats line: %v", line) } devName := fields[0] if isIgnoredDevice(devName) { continue } i := info.InterfaceStats{ Name: devName, } statFields := append(fields[1:5], fields[9:13]...) statPointers := []*uint64{ &i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped, &i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped, } err := setInterfaceStatValues(statFields, statPointers) if err != nil { return nil, fmt.Errorf("cannot parse interface stats (%v): %v", err, line) } stats = append(stats, i) } return stats, nil } func setInterfaceStatValues(fields []string, pointers []*uint64) error { for i, v := range fields { val, err := strconv.ParseUint(v, 10, 64) if err != nil { return err } *pointers[i] = val } return nil } func tcpStatsFromProc(rootFs string, pid int, file string) (info.TcpStat, error) { tcpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file) tcpStats, err := scanTcpStats(tcpStatsFile) if err != nil { return tcpStats, fmt.Errorf("couldn't read tcp stats: %v", err) } return tcpStats, nil } func scanTcpStats(tcpStatsFile string) (info.TcpStat, error) { var stats info.TcpStat data, err := ioutil.ReadFile(tcpStatsFile) if err != nil { return stats, fmt.Errorf("failure opening %s: %v", tcpStatsFile, err) } tcpStateMap := map[string]uint64{ "01": 0, //ESTABLISHED "02": 0, //SYN_SENT "03": 0, //SYN_RECV "04": 0, //FIN_WAIT1 "05": 0, //FIN_WAIT2 "06": 0, //TIME_WAIT "07": 0, //CLOSE "08": 0, //CLOSE_WAIT "09": 0, //LAST_ACK "0A": 0, //LISTEN "0B": 0, //CLOSING } reader := strings.NewReader(string(data)) scanner := bufio.NewScanner(reader) scanner.Split(bufio.ScanLines) // Discard header line if b := scanner.Scan(); !b { return stats, scanner.Err() } for scanner.Scan() { line := scanner.Text() state := strings.Fields(line) // TCP state is the 4th field. // Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode tcpState := state[3] _, ok := tcpStateMap[tcpState] if !ok { return stats, fmt.Errorf("invalid TCP stats line: %v", line) } tcpStateMap[tcpState]++ } stats = info.TcpStat{ Established: tcpStateMap["01"], SynSent: tcpStateMap["02"], SynRecv: tcpStateMap["03"], FinWait1: tcpStateMap["04"], FinWait2: tcpStateMap["05"], TimeWait: tcpStateMap["06"], Close: tcpStateMap["07"], CloseWait: tcpStateMap["08"], LastAck: tcpStateMap["09"], Listen: tcpStateMap["0A"], Closing: tcpStateMap["0B"], } return stats, nil } func udpStatsFromProc(rootFs string, pid int, file string) (info.UdpStat, error) { var err error var udpStats info.UdpStat udpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file) r, err := os.Open(udpStatsFile) if err != nil { return udpStats, fmt.Errorf("failure opening %s: %v", udpStatsFile, err) } udpStats, err = scanUdpStats(r) if err != nil { return udpStats, fmt.Errorf("couldn't read udp stats: %v", err) } return udpStats, nil } func scanUdpStats(r io.Reader) (info.UdpStat, error) { var stats info.UdpStat scanner := bufio.NewScanner(r) scanner.Split(bufio.ScanLines) // Discard header line if b := scanner.Scan(); !b { return stats, scanner.Err() } listening := uint64(0) dropped := uint64(0) rxQueued := uint64(0) txQueued := uint64(0) for scanner.Scan() { line := scanner.Text() // Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops listening++ fs := strings.Fields(line) if len(fs) != 13 { continue } rx, tx := uint64(0), uint64(0) fmt.Sscanf(fs[4], "%X:%X", &rx, &tx) rxQueued += rx txQueued += tx d, err := strconv.Atoi(string(fs[12])) if err != nil { continue } dropped += uint64(d) } stats = info.UdpStat{ Listen: listening, Dropped: dropped, RxQueued: rxQueued, TxQueued: txQueued, } return stats, nil } func GetProcesses(cgroupManager cgroups.Manager) ([]int, error) { pids, err := cgroupManager.GetPids() if err != nil { return nil, err } return pids, nil } func DiskStatsCopy0(major, minor uint64) *info.PerDiskStats { disk := info.PerDiskStats{ Major: major, Minor: minor, } disk.Stats = make(map[string]uint64) return &disk } type DiskKey struct { Major uint64 Minor uint64 } func DiskStatsCopy1(disk_stat map[DiskKey]*info.PerDiskStats) []info.PerDiskStats { i := 0 stat := make([]info.PerDiskStats, len(disk_stat)) for _, disk := range disk_stat { stat[i] = *disk i++ } return stat } func DiskStatsCopy(blkio_stats []cgroups.BlkioStatEntry) (stat []info.PerDiskStats) { if len(blkio_stats) == 0 { return } disk_stat := make(map[DiskKey]*info.PerDiskStats) for i := range blkio_stats { major := blkio_stats[i].Major minor := blkio_stats[i].Minor disk_key := DiskKey{ Major: major, Minor: minor, } diskp, ok := disk_stat[disk_key] if !ok { diskp = DiskStatsCopy0(major, minor) disk_stat[disk_key] = diskp } op := blkio_stats[i].Op if op == "" { op = "Count" } diskp.Stats[op] = blkio_stats[i].Value } return DiskStatsCopy1(disk_stat) } func minUint32(x, y uint32) uint32 { if x < y { return x } return y } // var to allow unit tests to stub it out var numCpusFunc = getNumberOnlineCPUs // Convert libcontainer stats to info.ContainerStats. func setCpuStats(s *cgroups.Stats, ret *info.ContainerStats) { ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode numPossible := uint32(len(s.CpuStats.CpuUsage.PercpuUsage)) // Note that as of https://patchwork.kernel.org/patch/8607101/ (kernel v4.7), // the percpu usage information includes extra zero values for all additional // possible CPUs. This is to allow statistic collection after CPU-hotplug. // We intentionally ignore these extra zeroes. numActual, err := numCpusFunc() if err != nil { glog.Errorf("unable to determine number of actual cpus; defaulting to maximum possible number: errno %v", err) numActual = numPossible } if numActual > numPossible { // The real number of cores should never be greater than the number of // datapoints reported in cpu usage. glog.Errorf("PercpuUsage had %v cpus, but the actual number is %v; ignoring extra CPUs", numPossible, numActual) } numActual = minUint32(numPossible, numActual) ret.Cpu.Usage.PerCpu = make([]uint64, numActual) ret.Cpu.Usage.Total = 0 for i := uint32(0); i < numActual; i++ { ret.Cpu.Usage.PerCpu[i] = s.CpuStats.CpuUsage.PercpuUsage[i] ret.Cpu.Usage.Total += s.CpuStats.CpuUsage.PercpuUsage[i] } ret.Cpu.CFS.Periods = s.CpuStats.ThrottlingData.Periods ret.Cpu.CFS.ThrottledPeriods = s.CpuStats.ThrottlingData.ThrottledPeriods ret.Cpu.CFS.ThrottledTime = s.CpuStats.ThrottlingData.ThrottledTime } // Copied from // https://github.com/moby/moby/blob/8b1adf55c2af329a4334f21d9444d6a169000c81/daemon/stats/collector_unix.go#L73 // Apache 2.0, Copyright Docker, Inc. func getNumberOnlineCPUs() (uint32, error) { i, err := C.sysconf(C._SC_NPROCESSORS_ONLN) // According to POSIX - errno is undefined after successful // sysconf, and can be non-zero in several cases, so look for // error in returned value not in errno. // (https://sourceware.org/bugzilla/show_bug.cgi?id=21536) if i == -1 { return 0, err } return uint32(i), nil } func setDiskIoStats(s *cgroups.Stats, ret *info.ContainerStats) { ret.DiskIo.IoServiceBytes = DiskStatsCopy(s.BlkioStats.IoServiceBytesRecursive) ret.DiskIo.IoServiced = DiskStatsCopy(s.BlkioStats.IoServicedRecursive) ret.DiskIo.IoQueued = DiskStatsCopy(s.BlkioStats.IoQueuedRecursive) ret.DiskIo.Sectors = DiskStatsCopy(s.BlkioStats.SectorsRecursive) ret.DiskIo.IoServiceTime = DiskStatsCopy(s.BlkioStats.IoServiceTimeRecursive) ret.DiskIo.IoWaitTime = DiskStatsCopy(s.BlkioStats.IoWaitTimeRecursive) ret.DiskIo.IoMerged = DiskStatsCopy(s.BlkioStats.IoMergedRecursive) ret.DiskIo.IoTime = DiskStatsCopy(s.BlkioStats.IoTimeRecursive) } func setMemoryStats(s *cgroups.Stats, ret *info.ContainerStats) { ret.Memory.Usage = s.MemoryStats.Usage.Usage ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt ret.Memory.Cache = s.MemoryStats.Stats["cache"] if s.MemoryStats.UseHierarchy { ret.Memory.RSS = s.MemoryStats.Stats["total_rss"] ret.Memory.Swap = s.MemoryStats.Stats["total_swap"] } else { ret.Memory.RSS = s.MemoryStats.Stats["rss"] ret.Memory.Swap = s.MemoryStats.Stats["swap"] } 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 } workingSet := ret.Memory.Usage if v, ok := s.MemoryStats.Stats["total_inactive_file"]; ok { if workingSet < v { workingSet = 0 } else { workingSet -= v } } ret.Memory.WorkingSet = workingSet } func setNetworkStats(libcontainerStats *libcontainer.Stats, ret *info.ContainerStats) { ret.Network.Interfaces = make([]info.InterfaceStats, len(libcontainerStats.Interfaces)) for i := range libcontainerStats.Interfaces { ret.Network.Interfaces[i] = info.InterfaceStats{ Name: libcontainerStats.Interfaces[i].Name, RxBytes: libcontainerStats.Interfaces[i].RxBytes, RxPackets: libcontainerStats.Interfaces[i].RxPackets, RxErrors: libcontainerStats.Interfaces[i].RxErrors, RxDropped: libcontainerStats.Interfaces[i].RxDropped, TxBytes: libcontainerStats.Interfaces[i].TxBytes, TxPackets: libcontainerStats.Interfaces[i].TxPackets, TxErrors: libcontainerStats.Interfaces[i].TxErrors, TxDropped: libcontainerStats.Interfaces[i].TxDropped, } } // Add to base struct for backwards compatibility. if len(ret.Network.Interfaces) > 0 { ret.Network.InterfaceStats = ret.Network.Interfaces[0] } } func newContainerStats(libcontainerStats *libcontainer.Stats) *info.ContainerStats { ret := &info.ContainerStats{ Timestamp: time.Now(), } if s := libcontainerStats.CgroupStats; s != nil { setCpuStats(s, ret) setDiskIoStats(s, ret) setMemoryStats(s, ret) } if len(libcontainerStats.Interfaces) > 0 { setNetworkStats(libcontainerStats, ret) } return ret } cadvisor-0.27.1/container/libcontainer/helpers_test.go000066400000000000000000000061531315410276000231020ustar00rootroot00000000000000// 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 libcontainer import ( "os" "testing" info "github.com/google/cadvisor/info/v1" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/system" ) func TestScanInterfaceStats(t *testing.T) { stats, err := scanInterfaceStats("testdata/procnetdev") if err != nil { t.Error(err) } var netdevstats = []info.InterfaceStats{ { Name: "wlp4s0", RxBytes: 1, RxPackets: 2, RxErrors: 3, RxDropped: 4, TxBytes: 9, TxPackets: 10, TxErrors: 11, TxDropped: 12, }, { Name: "em1", RxBytes: 315849, RxPackets: 1172, RxErrors: 0, RxDropped: 0, TxBytes: 315850, TxPackets: 1173, TxErrors: 0, TxDropped: 0, }, } if len(stats) != len(netdevstats) { t.Errorf("Expected 2 net stats, got %d", len(stats)) } for i, v := range netdevstats { if v != stats[i] { t.Errorf("Expected %#v, got %#v", v, stats[i]) } } } func TestScanUDPStats(t *testing.T) { udpStatsFile := "testdata/procnetudp" r, err := os.Open(udpStatsFile) if err != nil { t.Errorf("failure opening %s: %v", udpStatsFile, err) } stats, err := scanUdpStats(r) if err != nil { t.Error(err) } var udpstats = info.UdpStat{ Listen: 2, Dropped: 4, RxQueued: 10, TxQueued: 11, } if stats != udpstats { t.Errorf("Expected %#v, got %#v", udpstats, stats) } } // https://github.com/docker/libcontainer/blob/v2.2.1/cgroups/fs/cpuacct.go#L19 const nanosecondsInSeconds = 1000000000 var clockTicks = uint64(system.GetClockTicks()) func TestMorePossibleCPUs(t *testing.T) { realNumCPUs := uint32(8) numCpusFunc = func() (uint32, error) { return realNumCPUs, nil } possibleCPUs := uint32(31) perCpuUsage := make([]uint64, possibleCPUs) for i := uint32(0); i < realNumCPUs; i++ { perCpuUsage[i] = 8562955455524 } s := &cgroups.Stats{ CpuStats: cgroups.CpuStats{ CpuUsage: cgroups.CpuUsage{ PercpuUsage: perCpuUsage, TotalUsage: 33802947350272, UsageInKernelmode: 734746 * nanosecondsInSeconds / clockTicks, UsageInUsermode: 2767637 * nanosecondsInSeconds / clockTicks, }, }, } var ret info.ContainerStats setCpuStats(s, &ret) expected := info.ContainerStats{ Cpu: info.CpuStats{ Usage: info.CpuUsage{ PerCpu: perCpuUsage[0:realNumCPUs], User: s.CpuStats.CpuUsage.UsageInUsermode, System: s.CpuStats.CpuUsage.UsageInKernelmode, Total: 8562955455524 * uint64(realNumCPUs), }, }, } if !ret.Eq(&expected) { t.Fatalf("expected %+v == %+v", ret, expected) } } cadvisor-0.27.1/container/libcontainer/testdata/000077500000000000000000000000001315410276000216565ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.8.3/000077500000000000000000000000001315410276000237605ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.8.3/execdriver/000077500000000000000000000000001315410276000261205ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.8.3/execdriver/native/000077500000000000000000000000001315410276000274065ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.8.3/execdriver/native/1/000077500000000000000000000000001315410276000275465ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.8.3/execdriver/native/1/state.json000066400000000000000000000136121315410276000315640ustar00rootroot00000000000000{"id":"1","init_process_pid":66241,"init_process_start":"4507843","cgroup_paths":{"blkio":"/sys/fs/cgroup/blkio/docker/1","cpu":"/sys/fs/cgroup/cpu/docker/1","cpuacct":"/sys/fs/cgroup/cpuacct/docker/1","cpuset":"/sys/fs/cgroup/cpuset/docker/1","devices":"/sys/fs/cgroup/devices/docker/1","freezer":"/sys/fs/cgroup/freezer/docker/1","hugetlb":"/sys/fs/cgroup/hugetlb/docker/1","memory":"/sys/fs/cgroup/memory/docker/1","net_cls":"/sys/fs/cgroup/net_cls/docker/1","net_prio":"/sys/fs/cgroup/net_prio/docker/1","perf_event":"/sys/fs/cgroup/perf_event/docker/1"},"namespace_paths":{"NEWIPC":"/proc/66241/ns/ipc","NEWNET":"/var/run/docker/netns/e05ddb1777f3","NEWNS":"/proc/66241/ns/mnt","NEWPID":"/proc/66241/ns/pid","NEWUSER":"/proc/66241/ns/user","NEWUTS":"/proc/66241/ns/uts"},"config":{"no_pivot_root":false,"parent_death_signal":0,"pivot_dir":"","rootfs":"/var/lib/docker/devicemapper/mnt/1/rootfs","readonlyfs":false,"privatefs":true,"mounts":[{"source":"proc","destination":"/proc","device":"proc","flags":14,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"tmpfs","destination":"/dev","device":"tmpfs","flags":16777218,"data":"mode=755","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"devpts","destination":"/dev/pts","device":"devpts","flags":10,"data":"newinstance,ptmxmode=0666,mode=0620,gid=5","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"shm","destination":"/dev/shm","device":"tmpfs","flags":14,"data":"mode=1777,size=65536k","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"mqueue","destination":"/dev/mqueue","device":"mqueue","flags":14,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"sysfs","destination":"/sys","device":"sysfs","flags":15,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"cgroup","destination":"/sys/fs/cgroup","device":"cgroup","flags":15,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"/var/lib/docker/containers/1/resolv.conf","destination":"/etc/resolv.conf","device":"bind","flags":20480,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"/var/lib/docker/containers/1/hostname","destination":"/etc/hostname","device":"bind","flags":20480,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"/var/lib/docker/containers/1/hosts","destination":"/etc/hosts","device":"bind","flags":20480,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null}],"devices":[{"type":99,"path":"/dev/fuse","major":10,"minor":229,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/null","major":1,"minor":3,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/zero","major":1,"minor":5,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/full","major":1,"minor":7,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/tty","major":5,"minor":0,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/urandom","major":1,"minor":9,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/random","major":1,"minor":8,"permissions":"rwm","file_mode":438,"uid":0,"gid":0}],"mount_label":"","hostname":"1","namespaces":[{"type":"NEWNS","path":""},{"type":"NEWUTS","path":""},{"type":"NEWIPC","path":""},{"type":"NEWPID","path":""},{"type":"NEWNET","path":"/var/run/docker/netns/e05ddb1777f3"}],"capabilities":["CHOWN","DAC_OVERRIDE","FSETID","FOWNER","MKNOD","NET_RAW","SETGID","SETUID","SETFCAP","SETPCAP","NET_BIND_SERVICE","SYS_CHROOT","KILL","AUDIT_WRITE"],"networks":null,"routes":null,"cgroups":{"name":"1","parent":"docker","allow_all_devices":false,"allowed_devices":[{"type":99,"path":"","major":-1,"minor":-1,"permissions":"m","file_mode":0,"uid":0,"gid":0},{"type":98,"path":"","major":-1,"minor":-1,"permissions":"m","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/console","major":5,"minor":1,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/tty0","major":4,"minor":0,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/tty1","major":4,"minor":1,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"","major":136,"minor":-1,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"","major":5,"minor":2,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"","major":10,"minor":200,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/null","major":1,"minor":3,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/zero","major":1,"minor":5,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/full","major":1,"minor":7,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/tty","major":5,"minor":0,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/urandom","major":1,"minor":9,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/random","major":1,"minor":8,"permissions":"rwm","file_mode":438,"uid":0,"gid":0}],"denied_devices":null,"memory":0,"memory_reservation":0,"memory_swap":0,"kernel_memory":0,"cpu_shares":0,"cpuset_cpus":"","cpuset_mems":"","blkio_throttle_read_bps_device":"","blkio_throttle_write_bps_device":"","blkio_throttle_read_iops_device":"","blkio_throttle_write_iops_device":"","blkio_weight":0,"blkio_weight_device":"","freezer":"","hugetlb_limit":null,"slice":"","oom_kill_disable":false,"memory_swappiness":-1,"net_prio_ifpriomap":null,"net_cls_classid":""},"apparmor_profile":"docker-default","process_label":"","rlimits":null,"additional_groups":null,"uid_mappings":null,"gid_mappings":null,"mask_paths":["/proc/kcore","/proc/latency_stats","/proc/timer_stats"],"readonly_paths":["/proc/asound","/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"sysctl":null,"seccomp":null},"external_descriptors":["/dev/null","/dev/null","/dev/null"]} cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.9.1/000077500000000000000000000000001315410276000237575ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.9.1/execdriver/000077500000000000000000000000001315410276000261175ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.9.1/execdriver/native/000077500000000000000000000000001315410276000274055ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.9.1/execdriver/native/1/000077500000000000000000000000001315410276000275455ustar00rootroot00000000000000cadvisor-0.27.1/container/libcontainer/testdata/docker-v1.9.1/execdriver/native/1/state.json000066400000000000000000000143711315410276000315660ustar00rootroot00000000000000{"id":"1","init_process_pid":64076,"init_process_start":"3211353","cgroup_paths":{"blkio":"/sys/fs/cgroup/blkio/docker/1","cpu":"/sys/fs/cgroup/cpu/docker/1","cpuacct":"/sys/fs/cgroup/cpuacct/docker/1","cpuset":"/sys/fs/cgroup/cpuset/docker/1","devices":"/sys/fs/cgroup/devices/docker/1","freezer":"/sys/fs/cgroup/freezer/docker/1","hugetlb":"/sys/fs/cgroup/hugetlb/docker/1","memory":"/sys/fs/cgroup/memory/docker/1","net_cls":"/sys/fs/cgroup/net_cls/docker/1","net_prio":"/sys/fs/cgroup/net_prio/docker/1","perf_event":"/sys/fs/cgroup/perf_event/docker/1"},"namespace_paths":{"NEWIPC":"/proc/64076/ns/ipc","NEWNET":"/proc/64076/ns/net","NEWNS":"/proc/64076/ns/mnt","NEWPID":"/proc/64076/ns/pid","NEWUSER":"/proc/64076/ns/user","NEWUTS":"/proc/64076/ns/uts"},"config":{"no_pivot_root":false,"parent_death_signal":0,"pivot_dir":"","rootfs":"/var/lib/docker/devicemapper/mnt/1/rootfs","readonlyfs":false,"rootPropagation":278528,"mounts":[{"source":"proc","destination":"/proc","device":"proc","flags":14,"propagation_flags":null,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"tmpfs","destination":"/dev","device":"tmpfs","flags":16777218,"propagation_flags":null,"data":"mode=755","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"devpts","destination":"/dev/pts","device":"devpts","flags":10,"propagation_flags":null,"data":"newinstance,ptmxmode=0666,mode=0620,gid=5","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"sysfs","destination":"/sys","device":"sysfs","flags":15,"propagation_flags":null,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"cgroup","destination":"/sys/fs/cgroup","device":"cgroup","flags":15,"propagation_flags":null,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"/var/lib/docker/containers/1/resolv.conf","destination":"/etc/resolv.conf","device":"bind","flags":20480,"propagation_flags":null,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"/var/lib/docker/containers/1/hostname","destination":"/etc/hostname","device":"bind","flags":20480,"propagation_flags":null,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"/var/lib/docker/containers/1/hosts","destination":"/etc/hosts","device":"bind","flags":20480,"propagation_flags":null,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"/var/lib/docker/containers/1/shm","destination":"/dev/shm","device":"bind","flags":20480,"propagation_flags":null,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null},{"source":"/var/lib/docker/containers/1/mqueue","destination":"/dev/mqueue","device":"bind","flags":20480,"propagation_flags":null,"data":"","relabel":"","premount_cmds":null,"postmount_cmds":null}],"devices":[{"type":99,"path":"/dev/fuse","major":10,"minor":229,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/null","major":1,"minor":3,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/zero","major":1,"minor":5,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/full","major":1,"minor":7,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/tty","major":5,"minor":0,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/urandom","major":1,"minor":9,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/random","major":1,"minor":8,"permissions":"rwm","file_mode":438,"uid":0,"gid":0}],"mount_label":"","hostname":"1","namespaces":[{"type":"NEWNS","path":""},{"type":"NEWUTS","path":""},{"type":"NEWIPC","path":""},{"type":"NEWPID","path":""},{"type":"NEWNET","path":""}],"capabilities":["CAP_CHOWN","CAP_DAC_OVERRIDE","CAP_FSETID","CAP_FOWNER","CAP_MKNOD","CAP_NET_RAW","CAP_SETGID","CAP_SETUID","CAP_SETFCAP","CAP_SETPCAP","CAP_NET_BIND_SERVICE","CAP_SYS_CHROOT","CAP_KILL","CAP_AUDIT_WRITE"],"networks":null,"routes":null,"cgroups":{"name":"1","parent":"docker","allow_all_devices":false,"allowed_devices":[{"type":99,"path":"","major":-1,"minor":-1,"permissions":"m","file_mode":0,"uid":0,"gid":0},{"type":98,"path":"","major":-1,"minor":-1,"permissions":"m","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/console","major":5,"minor":1,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/tty0","major":4,"minor":0,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/tty1","major":4,"minor":1,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"","major":136,"minor":-1,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"","major":5,"minor":2,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"","major":10,"minor":200,"permissions":"rwm","file_mode":0,"uid":0,"gid":0},{"type":99,"path":"/dev/null","major":1,"minor":3,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/zero","major":1,"minor":5,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/full","major":1,"minor":7,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/tty","major":5,"minor":0,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/urandom","major":1,"minor":9,"permissions":"rwm","file_mode":438,"uid":0,"gid":0},{"type":99,"path":"/dev/random","major":1,"minor":8,"permissions":"rwm","file_mode":438,"uid":0,"gid":0}],"denied_devices":null,"memory":0,"memory_reservation":0,"memory_swap":0,"kernel_memory":0,"cpu_shares":0,"cpuset_cpus":"","cpuset_mems":"","blkio_weight":0,"blkio_leaf_weight":0,"blkio_weight_device":null,"blkio_throttle_read_bps_device":null,"blkio_throttle_write_bps_device":null,"blkio_throttle_read_iops_device":null,"blkio_throttle_write_iops_device":null,"freezer":"","hugetlb_limit":null,"slice":"","oom_kill_disable":false,"memory_swappiness":-1,"net_prio_ifpriomap":null,"net_cls_classid":""},"apparmor_profile":"docker-default","process_label":"","rlimits":null,"oom_score_adj":0,"additional_groups":null,"uid_mappings":null,"gid_mappings":null,"mask_paths":["/proc/kcore","/proc/latency_stats","/proc/timer_stats"],"readonly_paths":["/proc/asound","/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"],"sysctl":null,"seccomp":null,"version":""},"external_descriptors":["/dev/null","/dev/null","/dev/null"]} cadvisor-0.27.1/container/libcontainer/testdata/procnetdev000066400000000000000000000012521315410276000237520ustar00rootroot00000000000000Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed wlp4s0: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 docker0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 em1: 315849 1172 0 0 0 0 0 0 315850 1173 0 0 0 0 0 0cadvisor-0.27.1/container/libcontainer/testdata/procnetudp000066400000000000000000000005751315410276000237730ustar00rootroot00000000000000 sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops 1: 00000000:07D3 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 16583 2 ffff8800b4549400 0 21: 00000000:A841 00000000:0000 07 0000000A:0000000B 00:00000000 00000000 1000 0 114299623 2 ffff880338477800 4 cadvisor-0.27.1/container/raw/000077500000000000000000000000001315410276000161655ustar00rootroot00000000000000cadvisor-0.27.1/container/raw/factory.go000066400000000000000000000057661315410276000202010ustar00rootroot00000000000000// 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 raw import ( "flag" "fmt" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/common" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" watch "github.com/google/cadvisor/manager/watcher" "github.com/golang/glog" ) var dockerOnly = flag.Bool("docker_only", false, "Only report docker containers in addition to root stats") type rawFactory struct { // Factory for machine information. machineInfoFactory info.MachineInfoFactory // Information about the cgroup subsystems. cgroupSubsystems *libcontainer.CgroupSubsystems // Information about mounted filesystems. fsInfo fs.FsInfo // Watcher for inotify events. watcher *common.InotifyWatcher // List of metrics to be ignored. ignoreMetrics map[container.MetricKind]struct{} } func (self *rawFactory) String() string { return "raw" } func (self *rawFactory) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) { rootFs := "/" if !inHostNamespace { rootFs = "/rootfs" } return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo, self.watcher, rootFs, self.ignoreMetrics) } // The raw factory can handle any container. If --docker_only is set to false, non-docker containers are ignored. func (self *rawFactory) CanHandleAndAccept(name string) (bool, bool, error) { accept := name == "/" || !*dockerOnly return true, accept, nil } func (self *rawFactory) DebugInfo() map[string][]string { return common.DebugInfo(self.watcher.GetWatches()) } func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics map[container.MetricKind]struct{}) error { cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() if err != nil { return fmt.Errorf("failed to get cgroup subsystems: %v", err) } if len(cgroupSubsystems.Mounts) == 0 { return fmt.Errorf("failed to find supported cgroup mounts for the raw factory") } watcher, err := common.NewInotifyWatcher() if err != nil { return err } glog.Infof("Registering Raw factory") factory := &rawFactory{ machineInfoFactory: machineInfoFactory, fsInfo: fsInfo, cgroupSubsystems: &cgroupSubsystems, watcher: watcher, ignoreMetrics: ignoreMetrics, } container.RegisterContainerHandlerFactory(factory, []watch.ContainerWatchSource{watch.Raw}) return nil } cadvisor-0.27.1/container/raw/handler.go000066400000000000000000000206721315410276000201400ustar00rootroot00000000000000// 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 "raw" containers. package raw import ( "fmt" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/common" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/machine" "github.com/golang/glog" "github.com/opencontainers/runc/libcontainer/cgroups" cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" "github.com/opencontainers/runc/libcontainer/configs" ) type rawContainerHandler struct { // Name of the container for this handler. name string cgroupSubsystems *libcontainer.CgroupSubsystems machineInfoFactory info.MachineInfoFactory // Absolute path to the cgroup hierarchies of this container. // (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test") cgroupPaths map[string]string // Manager of this container's cgroups. cgroupManager cgroups.Manager fsInfo fs.FsInfo externalMounts []common.Mount rootFs string // Metrics to be ignored. ignoreMetrics container.MetricSet pid int } func isRootCgroup(name string) bool { return name == "/" } func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, watcher *common.InotifyWatcher, rootFs string, ignoreMetrics container.MetricSet) (container.ContainerHandler, error) { cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name) cHints, err := common.GetContainerHintsFromFile(*common.ArgContainerHints) if err != nil { return nil, err } // Generate the equivalent cgroup manager for this container. cgroupManager := &cgroupfs.Manager{ Cgroups: &configs.Cgroup{ Name: name, }, Paths: cgroupPaths, } var externalMounts []common.Mount for _, container := range cHints.AllHosts { if name == container.FullName { externalMounts = container.Mounts break } } pid := 0 if isRootCgroup(name) { pid = 1 } return &rawContainerHandler{ name: name, cgroupSubsystems: cgroupSubsystems, machineInfoFactory: machineInfoFactory, cgroupPaths: cgroupPaths, cgroupManager: cgroupManager, fsInfo: fsInfo, externalMounts: externalMounts, rootFs: rootFs, ignoreMetrics: ignoreMetrics, pid: pid, }, nil } func (self *rawContainerHandler) ContainerReference() (info.ContainerReference, error) { // We only know the container by its one name. return info.ContainerReference{ Name: self.name, }, nil } func (self *rawContainerHandler) GetRootNetworkDevices() ([]info.NetInfo, error) { nd := []info.NetInfo{} if isRootCgroup(self.name) { mi, err := self.machineInfoFactory.GetMachineInfo() if err != nil { return nd, err } return mi.NetworkDevices, nil } return nd, nil } // Nothing to start up. func (self *rawContainerHandler) Start() {} // Nothing to clean up. func (self *rawContainerHandler) Cleanup() {} func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) { const hasNetwork = false hasFilesystem := isRootCgroup(self.name) || len(self.externalMounts) > 0 spec, err := common.GetSpec(self.cgroupPaths, self.machineInfoFactory, hasNetwork, hasFilesystem) if err != nil { return spec, err } if isRootCgroup(self.name) { // Check physical network devices for root container. nd, err := self.GetRootNetworkDevices() if err != nil { return spec, err } spec.HasNetwork = spec.HasNetwork || len(nd) != 0 // Get memory and swap limits of the running machine memLimit, err := machine.GetMachineMemoryCapacity() if err != nil { glog.Warningf("failed to obtain memory limit for machine container") spec.HasMemory = false } else { spec.Memory.Limit = uint64(memLimit) // Spec is marked to have memory only if the memory limit is set spec.HasMemory = true } swapLimit, err := machine.GetMachineSwapCapacity() if err != nil { glog.Warningf("failed to obtain swap limit for machine container") } else { spec.Memory.SwapLimit = uint64(swapLimit) } } return spec, nil } func fsToFsStats(fs *fs.Fs) info.FsStats { inodes := uint64(0) inodesFree := uint64(0) hasInodes := fs.InodesFree != nil if hasInodes { inodes = *fs.Inodes inodesFree = *fs.InodesFree } return info.FsStats{ Device: fs.Device, Type: fs.Type.String(), Limit: fs.Capacity, Usage: fs.Capacity - fs.Free, HasInodes: hasInodes, Inodes: inodes, InodesFree: inodesFree, Available: fs.Available, ReadsCompleted: fs.DiskStats.ReadsCompleted, ReadsMerged: fs.DiskStats.ReadsMerged, SectorsRead: fs.DiskStats.SectorsRead, ReadTime: fs.DiskStats.ReadTime, WritesCompleted: fs.DiskStats.WritesCompleted, WritesMerged: fs.DiskStats.WritesMerged, SectorsWritten: fs.DiskStats.SectorsWritten, WriteTime: fs.DiskStats.WriteTime, IoInProgress: fs.DiskStats.IoInProgress, IoTime: fs.DiskStats.IoTime, WeightedIoTime: fs.DiskStats.WeightedIoTime, } } func (self *rawContainerHandler) getFsStats(stats *info.ContainerStats) error { var allFs []fs.Fs // Get Filesystem information only for the root cgroup. if isRootCgroup(self.name) { filesystems, err := self.fsInfo.GetGlobalFsInfo() if err != nil { return err } for i := range filesystems { fs := filesystems[i] stats.Filesystem = append(stats.Filesystem, fsToFsStats(&fs)) } allFs = filesystems } else if len(self.externalMounts) > 0 { var mountSet map[string]struct{} mountSet = make(map[string]struct{}) for _, mount := range self.externalMounts { mountSet[mount.HostDir] = struct{}{} } filesystems, err := self.fsInfo.GetFsInfoForPath(mountSet) if err != nil { return err } for i := range filesystems { fs := filesystems[i] stats.Filesystem = append(stats.Filesystem, fsToFsStats(&fs)) } allFs = filesystems } common.AssignDeviceNamesToDiskStats(&fsNamer{fs: allFs, factory: self.machineInfoFactory}, &stats.DiskIo) return nil } func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) { stats, err := libcontainer.GetStats(self.cgroupManager, self.rootFs, self.pid, self.ignoreMetrics) if err != nil { return stats, err } // Get filesystem stats. err = self.getFsStats(stats) if err != nil { return stats, err } return stats, nil } func (self *rawContainerHandler) GetCgroupPath(resource string) (string, error) { path, ok := self.cgroupPaths[resource] if !ok { return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, self.name) } return path, nil } func (self *rawContainerHandler) GetContainerLabels() map[string]string { return map[string]string{} } func (self *rawContainerHandler) GetContainerIPAddress() string { // the IP address for the raw container corresponds to the system ip address. return "127.0.0.1" } func (self *rawContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { return common.ListContainers(self.name, self.cgroupPaths, listType) } func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { return libcontainer.GetProcesses(self.cgroupManager) } func (self *rawContainerHandler) Exists() bool { return common.CgroupExists(self.cgroupPaths) } func (self *rawContainerHandler) Type() container.ContainerType { return container.ContainerTypeRaw } type fsNamer struct { fs []fs.Fs factory info.MachineInfoFactory info common.DeviceNamer } func (n *fsNamer) DeviceName(major, minor uint64) (string, bool) { for _, info := range n.fs { if uint64(info.Major) == major && uint64(info.Minor) == minor { return info.Device, true } } if n.info == nil { mi, err := n.factory.GetMachineInfo() if err != nil { return "", false } n.info = (*common.MachineInfoNamer)(mi) } return n.info.DeviceName(major, minor) } cadvisor-0.27.1/container/raw/handler_test.go000066400000000000000000000073731315410276000212020ustar00rootroot00000000000000// Copyright 2016 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 "raw" containers. package raw import ( "reflect" "testing" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" ) func TestFsToFsStats(t *testing.T) { inodes := uint64(100) inodesFree := uint64(50) testCases := map[string]struct { fs *fs.Fs expected info.FsStats }{ "has_inodes": { fs: &fs.Fs{ DeviceInfo: fs.DeviceInfo{Device: "123"}, Type: fs.VFS, Capacity: uint64(1024 * 1024), Free: uint64(1024), Available: uint64(1024), Inodes: &inodes, InodesFree: &inodesFree, DiskStats: fs.DiskStats{ ReadsCompleted: uint64(100), ReadsMerged: uint64(100), SectorsRead: uint64(100), ReadTime: uint64(100), WritesCompleted: uint64(100), WritesMerged: uint64(100), SectorsWritten: uint64(100), WriteTime: uint64(100), IoInProgress: uint64(100), IoTime: uint64(100), WeightedIoTime: uint64(100), }, }, expected: info.FsStats{ Device: "123", Type: fs.VFS.String(), Limit: uint64(1024 * 1024), Usage: uint64(1024*1024) - uint64(1024), HasInodes: true, Inodes: inodes, InodesFree: inodesFree, Available: uint64(1024), ReadsCompleted: uint64(100), ReadsMerged: uint64(100), SectorsRead: uint64(100), ReadTime: uint64(100), WritesCompleted: uint64(100), WritesMerged: uint64(100), SectorsWritten: uint64(100), WriteTime: uint64(100), IoInProgress: uint64(100), IoTime: uint64(100), WeightedIoTime: uint64(100), }, }, "has_no_inodes": { fs: &fs.Fs{ DeviceInfo: fs.DeviceInfo{Device: "123"}, Type: fs.DeviceMapper, Capacity: uint64(1024 * 1024), Free: uint64(1024), Available: uint64(1024), DiskStats: fs.DiskStats{ ReadsCompleted: uint64(100), ReadsMerged: uint64(100), SectorsRead: uint64(100), ReadTime: uint64(100), WritesCompleted: uint64(100), WritesMerged: uint64(100), SectorsWritten: uint64(100), WriteTime: uint64(100), IoInProgress: uint64(100), IoTime: uint64(100), WeightedIoTime: uint64(100), }, }, expected: info.FsStats{ Device: "123", Type: fs.DeviceMapper.String(), Limit: uint64(1024 * 1024), Usage: uint64(1024*1024) - uint64(1024), HasInodes: false, Available: uint64(1024), ReadsCompleted: uint64(100), ReadsMerged: uint64(100), SectorsRead: uint64(100), ReadTime: uint64(100), WritesCompleted: uint64(100), WritesMerged: uint64(100), SectorsWritten: uint64(100), WriteTime: uint64(100), IoInProgress: uint64(100), IoTime: uint64(100), WeightedIoTime: uint64(100), }, }, } for testName, testCase := range testCases { actual := fsToFsStats(testCase.fs) if !reflect.DeepEqual(testCase.expected, actual) { t.Errorf("test case=%v, expected=%v, actual=%v", testName, testCase.expected, actual) } } } cadvisor-0.27.1/container/rkt/000077500000000000000000000000001315410276000161745ustar00rootroot00000000000000cadvisor-0.27.1/container/rkt/client.go000066400000000000000000000047061315410276000200100ustar00rootroot00000000000000// Copyright 2016 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 rkt import ( "fmt" "net" "sync" "time" "github.com/blang/semver" rktapi "github.com/coreos/rkt/api/v1alpha" "golang.org/x/net/context" "google.golang.org/grpc" ) const ( defaultRktAPIServiceAddr = "localhost:15441" timeout = 2 * time.Second minimumRktBinVersion = "1.6.0" ) var ( rktClient rktapi.PublicAPIClient rktClientErr error once sync.Once ) func Client() (rktapi.PublicAPIClient, error) { once.Do(func() { conn, err := net.DialTimeout("tcp", defaultRktAPIServiceAddr, timeout) if err != nil { rktClient = nil rktClientErr = fmt.Errorf("rkt: cannot tcp Dial rkt api service: %v", err) return } conn.Close() apisvcConn, err := grpc.Dial(defaultRktAPIServiceAddr, grpc.WithInsecure(), grpc.WithTimeout(timeout)) if err != nil { rktClient = nil rktClientErr = fmt.Errorf("rkt: cannot grpc Dial rkt api service: %v", err) return } apisvc := rktapi.NewPublicAPIClient(apisvcConn) resp, err := apisvc.GetInfo(context.Background(), &rktapi.GetInfoRequest{}) if err != nil { rktClientErr = fmt.Errorf("rkt: GetInfo() failed: %v", err) return } binVersion, err := semver.Make(resp.Info.RktVersion) if err != nil { rktClientErr = fmt.Errorf("rkt: couldn't parse RtVersion: %v", err) return } if binVersion.LT(semver.MustParse(minimumRktBinVersion)) { rktClientErr = fmt.Errorf("rkt: binary version is too old(%v), requires at least %v", resp.Info.RktVersion, minimumRktBinVersion) return } rktClient = apisvc }) return rktClient, rktClientErr } func RktPath() (string, error) { client, err := Client() if err != nil { return "", err } resp, err := client.GetInfo(context.Background(), &rktapi.GetInfoRequest{}) if err != nil { return "", fmt.Errorf("couldn't GetInfo from rkt api service: %v", err) } return resp.Info.GlobalFlags.Dir, nil } cadvisor-0.27.1/container/rkt/client_test.go000066400000000000000000000015401315410276000210400ustar00rootroot00000000000000// Copyright 2016 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 rkt import ( "testing" "github.com/blang/semver" ) func TestMinParse(t *testing.T) { _, err := semver.Make(minimumRktBinVersion) if err != nil { t.Errorf("Couldn't parse the minimumRktBinVersion(%v): %v", minimumRktBinVersion, err) } } cadvisor-0.27.1/container/rkt/factory.go000066400000000000000000000053241315410276000201760ustar00rootroot00000000000000// Copyright 2016 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 rkt import ( "fmt" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/manager/watcher" "github.com/golang/glog" ) const RktNamespace = "rkt" type rktFactory struct { machineInfoFactory info.MachineInfoFactory cgroupSubsystems *libcontainer.CgroupSubsystems fsInfo fs.FsInfo ignoreMetrics container.MetricSet rktPath string } func (self *rktFactory) String() string { return "rkt" } func (self *rktFactory) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) { client, err := Client() if err != nil { return nil, err } rootFs := "/" if !inHostNamespace { rootFs = "/rootfs" } return newRktContainerHandler(name, client, self.rktPath, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo, rootFs, self.ignoreMetrics) } func (self *rktFactory) CanHandleAndAccept(name string) (bool, bool, error) { accept, err := verifyPod(name) return accept, accept, err } func (self *rktFactory) DebugInfo() map[string][]string { return map[string][]string{} } func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error { _, err := Client() if err != nil { return fmt.Errorf("unable to communicate with Rkt api service: %v", err) } rktPath, err := RktPath() if err != nil { return fmt.Errorf("unable to get the RktPath variable %v", err) } cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() if err != nil { return fmt.Errorf("failed to get cgroup subsystems: %v", err) } if len(cgroupSubsystems.Mounts) == 0 { return fmt.Errorf("failed to find supported cgroup mounts for the raw factory") } glog.Infof("Registering Rkt factory") factory := &rktFactory{ machineInfoFactory: machineInfoFactory, fsInfo: fsInfo, cgroupSubsystems: &cgroupSubsystems, ignoreMetrics: ignoreMetrics, rktPath: rktPath, } container.RegisterContainerHandlerFactory(factory, []watcher.ContainerWatchSource{watcher.Rkt}) return nil } cadvisor-0.27.1/container/rkt/handler.go000066400000000000000000000202351315410276000201420ustar00rootroot00000000000000// Copyright 2016 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 "rkt" containers. package rkt import ( "fmt" "os" rktapi "github.com/coreos/rkt/api/v1alpha" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/common" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "golang.org/x/net/context" "github.com/golang/glog" "github.com/opencontainers/runc/libcontainer/cgroups" cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" "github.com/opencontainers/runc/libcontainer/configs" ) type rktContainerHandler struct { rktClient rktapi.PublicAPIClient // Name of the container for this handler. name string cgroupSubsystems *libcontainer.CgroupSubsystems machineInfoFactory info.MachineInfoFactory // Absolute path to the cgroup hierarchies of this container. // (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test") cgroupPaths map[string]string // Manager of this container's cgroups. cgroupManager cgroups.Manager // Whether this container has network isolation enabled. hasNetwork bool fsInfo fs.FsInfo rootFs string isPod bool aliases []string pid int rootfsStorageDir string labels map[string]string // Filesystem handler. fsHandler common.FsHandler ignoreMetrics container.MetricSet apiPod *rktapi.Pod } func newRktContainerHandler(name string, rktClient rktapi.PublicAPIClient, rktPath string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, rootFs string, ignoreMetrics container.MetricSet) (container.ContainerHandler, error) { aliases := make([]string, 1) isPod := false apiPod := &rktapi.Pod{} parsed, err := parseName(name) if err != nil { return nil, fmt.Errorf("this should be impossible!, new handler failing, but factory allowed, name = %s", name) } // rktnetes uses containerID: rkt://fff40827-b994-4e3a-8f88-6427c2c8a5ac:nginx if parsed.Container == "" { isPod = true aliases = append(aliases, "rkt://"+parsed.Pod) } else { aliases = append(aliases, "rkt://"+parsed.Pod+":"+parsed.Container) } pid := os.Getpid() labels := make(map[string]string) resp, err := rktClient.InspectPod(context.Background(), &rktapi.InspectPodRequest{ Id: parsed.Pod, }) if err != nil { return nil, err } annotations := resp.Pod.Annotations if parsed.Container != "" { // As not empty string, an App container if contAnnotations, ok := findAnnotations(resp.Pod.Apps, parsed.Container); !ok { glog.Warningf("couldn't find app %v in pod", parsed.Container) } else { annotations = append(annotations, contAnnotations...) } } else { // The Pod container pid = int(resp.Pod.Pid) apiPod = resp.Pod } labels = createLabels(annotations) cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name) // Generate the equivalent cgroup manager for this container. cgroupManager := &cgroupfs.Manager{ Cgroups: &configs.Cgroup{ Name: name, }, Paths: cgroupPaths, } hasNetwork := false if isPod { hasNetwork = true } rootfsStorageDir := getRootFs(rktPath, parsed) handler := &rktContainerHandler{ name: name, rktClient: rktClient, cgroupSubsystems: cgroupSubsystems, machineInfoFactory: machineInfoFactory, cgroupPaths: cgroupPaths, cgroupManager: cgroupManager, fsInfo: fsInfo, hasNetwork: hasNetwork, rootFs: rootFs, isPod: isPod, aliases: aliases, pid: pid, labels: labels, rootfsStorageDir: rootfsStorageDir, ignoreMetrics: ignoreMetrics, apiPod: apiPod, } if !ignoreMetrics.Has(container.DiskUsageMetrics) { handler.fsHandler = common.NewFsHandler(common.DefaultPeriod, rootfsStorageDir, "", fsInfo) } return handler, nil } func findAnnotations(apps []*rktapi.App, container string) ([]*rktapi.KeyValue, bool) { for _, app := range apps { if app.Name == container { return app.Annotations, true } } return nil, false } func createLabels(annotations []*rktapi.KeyValue) map[string]string { labels := make(map[string]string) for _, kv := range annotations { labels[kv.Key] = kv.Value } return labels } func (handler *rktContainerHandler) ContainerReference() (info.ContainerReference, error) { return info.ContainerReference{ Name: handler.name, Aliases: handler.aliases, Namespace: RktNamespace, Labels: handler.labels, }, nil } func (handler *rktContainerHandler) Start() { handler.fsHandler.Start() } func (handler *rktContainerHandler) Cleanup() { handler.fsHandler.Stop() } func (handler *rktContainerHandler) GetSpec() (info.ContainerSpec, error) { hasNetwork := handler.hasNetwork && !handler.ignoreMetrics.Has(container.NetworkUsageMetrics) hasFilesystem := !handler.ignoreMetrics.Has(container.DiskUsageMetrics) spec, err := common.GetSpec(handler.cgroupPaths, handler.machineInfoFactory, hasNetwork, hasFilesystem) spec.Labels = handler.labels return spec, err } func (handler *rktContainerHandler) getFsStats(stats *info.ContainerStats) error { mi, err := handler.machineInfoFactory.GetMachineInfo() if err != nil { return err } if !handler.ignoreMetrics.Has(container.DiskIOMetrics) { common.AssignDeviceNamesToDiskStats((*common.MachineInfoNamer)(mi), &stats.DiskIo) } if handler.ignoreMetrics.Has(container.DiskUsageMetrics) { return nil } deviceInfo, err := handler.fsInfo.GetDirFsDevice(handler.rootfsStorageDir) if err != nil { return err } var limit uint64 = 0 // Use capacity as limit. for _, fs := range mi.Filesystems { if fs.Device == deviceInfo.Device { limit = fs.Capacity break } } fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit} usage := handler.fsHandler.Usage() fsStat.BaseUsage = usage.BaseUsageBytes fsStat.Usage = usage.TotalUsageBytes fsStat.Inodes = usage.InodeUsage stats.Filesystem = append(stats.Filesystem, fsStat) return nil } func (handler *rktContainerHandler) GetStats() (*info.ContainerStats, error) { stats, err := libcontainer.GetStats(handler.cgroupManager, handler.rootFs, handler.pid, handler.ignoreMetrics) if err != nil { return stats, err } // Get filesystem stats. err = handler.getFsStats(stats) if err != nil { return stats, err } return stats, nil } func (self *rktContainerHandler) GetContainerIPAddress() string { // attempt to return the ip address of the pod // if a specific ip address of the pod could not be determined, return the system ip address if self.isPod && len(self.apiPod.Networks) > 0 { address := self.apiPod.Networks[0].Ipv4 if address != "" { return address } else { return self.apiPod.Networks[0].Ipv6 } } else { return "127.0.0.1" } } func (handler *rktContainerHandler) GetCgroupPath(resource string) (string, error) { path, ok := handler.cgroupPaths[resource] if !ok { return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, handler.name) } return path, nil } func (handler *rktContainerHandler) GetContainerLabels() map[string]string { return handler.labels } func (handler *rktContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { return common.ListContainers(handler.name, handler.cgroupPaths, listType) } func (handler *rktContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { return libcontainer.GetProcesses(handler.cgroupManager) } func (handler *rktContainerHandler) Exists() bool { return common.CgroupExists(handler.cgroupPaths) } func (handler *rktContainerHandler) Type() container.ContainerType { return container.ContainerTypeRkt } cadvisor-0.27.1/container/rkt/helpers.go000066400000000000000000000075741315410276000202020ustar00rootroot00000000000000// Copyright 2016 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 rkt import ( "fmt" "io/ioutil" "path" "strings" rktapi "github.com/coreos/rkt/api/v1alpha" "github.com/golang/glog" "golang.org/x/net/context" ) type parsedName struct { Pod string Container string } func verifyPod(name string) (bool, error) { pod, err := cgroupToPod(name) if err != nil || pod == nil { return false, err } // Anything handler can handle is also accepted. // Accept cgroups that are sub the pod cgroup, except "system.slice" // - "system.slice" doesn't contain any processes itself accept := !strings.HasSuffix(name, "/system.slice") return accept, nil } func cgroupToPod(name string) (*rktapi.Pod, error) { rktClient, err := Client() if err != nil { return nil, fmt.Errorf("couldn't get rkt api service: %v", err) } resp, err := rktClient.ListPods(context.Background(), &rktapi.ListPodsRequest{ Filters: []*rktapi.PodFilter{ { States: []rktapi.PodState{rktapi.PodState_POD_STATE_RUNNING}, PodSubCgroups: []string{name}, }, }, }) if err != nil { return nil, fmt.Errorf("failed to list pods: %v", err) } if len(resp.Pods) == 0 { return nil, nil } if len(resp.Pods) != 1 { return nil, fmt.Errorf("returned %d (expected 1) pods for cgroup %v", len(resp.Pods), name) } return resp.Pods[0], nil } /* Parse cgroup name into a pod/container name struct Example cgroup fs name pod - /machine.slice/machine-rkt\\x2df556b64a\\x2d17a7\\x2d47d7\\x2d93ec\\x2def2275c3d67e.scope/ or /system.slice/k8s-..../ container under pod - /machine.slice/machine-rkt\\x2df556b64a\\x2d17a7\\x2d47d7\\x2d93ec\\x2def2275c3d67e.scope/system.slice/alpine-sh.service or /system.slice/k8s-..../system.slice/pause.service */ func parseName(name string) (*parsedName, error) { pod, err := cgroupToPod(name) if err != nil { return nil, fmt.Errorf("parseName: couldn't convert %v to a rkt pod: %v", name, err) } if pod == nil { return nil, fmt.Errorf("parseName: didn't return a pod for %v", name) } splits := strings.Split(name, "/") parsed := &parsedName{} if len(splits) == 3 || len(splits) == 5 { parsed.Pod = pod.Id if len(splits) == 5 { parsed.Container = strings.Replace(splits[4], ".service", "", -1) } return parsed, nil } return nil, fmt.Errorf("%s not handled by rkt handler", name) } // Gets a Rkt container's overlay upper dir func getRootFs(root string, parsed *parsedName) string { /* Example of where it stores the upper dir key for container /var/lib/rkt/pods/run/bc793ec6-c48f-4480-99b5-6bec16d52210/appsinfo/alpine-sh/treeStoreID for pod /var/lib/rkt/pods/run/f556b64a-17a7-47d7-93ec-ef2275c3d67e/stage1TreeStoreID */ var tree string if parsed.Container == "" { tree = path.Join(root, "pods/run", parsed.Pod, "stage1TreeStoreID") } else { tree = path.Join(root, "pods/run", parsed.Pod, "appsinfo", parsed.Container, "treeStoreID") } bytes, err := ioutil.ReadFile(tree) if err != nil { glog.Errorf("ReadFile failed, couldn't read %v to get upper dir: %v", tree, err) return "" } s := string(bytes) /* Example of where the upper dir is stored via key read above /var/lib/rkt/pods/run/bc793ec6-c48f-4480-99b5-6bec16d52210/overlay/deps-sha512-82a099e560a596662b15dec835e9adabab539cad1f41776a30195a01a8f2f22b/ */ return path.Join(root, "pods/run", parsed.Pod, "overlay", s) } cadvisor-0.27.1/container/systemd/000077500000000000000000000000001315410276000170645ustar00rootroot00000000000000cadvisor-0.27.1/container/systemd/factory.go000066400000000000000000000036721315410276000210720ustar00rootroot00000000000000// Copyright 2016 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 systemd import ( "fmt" "strings" "github.com/google/cadvisor/container" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/manager/watcher" "github.com/golang/glog" ) type systemdFactory struct{} func (f *systemdFactory) String() string { return "systemd" } func (f *systemdFactory) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) { return nil, fmt.Errorf("Not yet supported") } func (f *systemdFactory) CanHandleAndAccept(name string) (bool, bool, error) { // on systemd using devicemapper each mount into the container has an associated cgroup that we ignore. // for details on .mount units: http://man7.org/linux/man-pages/man5/systemd.mount.5.html if strings.HasSuffix(name, ".mount") { return true, false, nil } return false, false, fmt.Errorf("%s not handled by systemd handler", name) } func (f *systemdFactory) DebugInfo() map[string][]string { return map[string][]string{} } // Register registers the systemd container factory. func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error { glog.Infof("Registering systemd factory") factory := &systemdFactory{} container.RegisterContainerHandlerFactory(factory, []watcher.ContainerWatchSource{watcher.Raw}) return nil } cadvisor-0.27.1/container/testing/000077500000000000000000000000001315410276000170515ustar00rootroot00000000000000cadvisor-0.27.1/container/testing/mock_handler.go000066400000000000000000000071471315410276000220370ustar00rootroot00000000000000// 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 testing import ( "github.com/google/cadvisor/container" info "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/mock" ) // This struct mocks a container handler. type MockContainerHandler struct { mock.Mock Name string Aliases []string } func NewMockContainerHandler(containerName string) *MockContainerHandler { return &MockContainerHandler{ Name: containerName, } } // If self.Name is not empty, then ContainerReference() will return self.Name and self.Aliases. // Otherwise, it will use the value provided by .On().Return(). func (self *MockContainerHandler) ContainerReference() (info.ContainerReference, error) { if len(self.Name) > 0 { var aliases []string if len(self.Aliases) > 0 { aliases = make([]string, len(self.Aliases)) copy(aliases, self.Aliases) } return info.ContainerReference{ Name: self.Name, Aliases: aliases, }, nil } args := self.Called() return args.Get(0).(info.ContainerReference), args.Error(1) } func (self *MockContainerHandler) Start() {} func (self *MockContainerHandler) Cleanup() {} func (self *MockContainerHandler) GetSpec() (info.ContainerSpec, error) { args := self.Called() return args.Get(0).(info.ContainerSpec), args.Error(1) } func (self *MockContainerHandler) GetStats() (*info.ContainerStats, error) { args := self.Called() return args.Get(0).(*info.ContainerStats), args.Error(1) } func (self *MockContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { args := self.Called(listType) return args.Get(0).([]info.ContainerReference), args.Error(1) } func (self *MockContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { args := self.Called(listType) return args.Get(0).([]int), args.Error(1) } func (self *MockContainerHandler) Exists() bool { args := self.Called() return args.Get(0).(bool) } func (self *MockContainerHandler) GetCgroupPath(path string) (string, error) { args := self.Called(path) return args.Get(0).(string), args.Error(1) } func (self *MockContainerHandler) GetContainerLabels() map[string]string { args := self.Called() return args.Get(0).(map[string]string) } func (self *MockContainerHandler) Type() container.ContainerType { args := self.Called() return args.Get(0).(container.ContainerType) } func (self *MockContainerHandler) GetContainerIPAddress() string { args := self.Called() return args.Get(0).(string) } type FactoryForMockContainerHandler struct { Name string PrepareContainerHandlerFunc func(name string, handler *MockContainerHandler) } func (self *FactoryForMockContainerHandler) String() string { return self.Name } func (self *FactoryForMockContainerHandler) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) { handler := &MockContainerHandler{} if self.PrepareContainerHandlerFunc != nil { self.PrepareContainerHandlerFunc(name, handler) } return handler, nil } func (self *FactoryForMockContainerHandler) CanHandle(name string) bool { return true } cadvisor-0.27.1/deploy/000077500000000000000000000000001315410276000147065ustar00rootroot00000000000000cadvisor-0.27.1/deploy/Dockerfile000066400000000000000000000022751315410276000167060ustar00rootroot00000000000000FROM alpine:3.4 MAINTAINER dengnan@google.com vmarmol@google.com vishnuk@google.com jimmidyson@gmail.com stclair@google.com ENV GLIBC_VERSION "2.23-r3" RUN apk --no-cache add ca-certificates wget device-mapper findutils && \ apk --no-cache add zfs --repository http://dl-3.alpinelinux.org/alpine/edge/main/ && \ apk --no-cache add thin-provisioning-tools --repository http://dl-3.alpinelinux.org/alpine/edge/main/ && \ wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub && \ wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-${GLIBC_VERSION}.apk && \ wget https://github.com/andyshinn/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-bin-${GLIBC_VERSION}.apk && \ apk add glibc-${GLIBC_VERSION}.apk glibc-bin-${GLIBC_VERSION}.apk && \ /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib && \ echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ rm -rf /var/cache/apk/* # Grab cadvisor from the staging directory. ADD cadvisor /usr/bin/cadvisor EXPOSE 8080 ENTRYPOINT ["/usr/bin/cadvisor", "-logtostderr"] cadvisor-0.27.1/deploy/build.sh000077500000000000000000000012441315410276000163450ustar00rootroot00000000000000#!/bin/bash # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -e set -x make build docker build -t google/cadvisor:beta . cadvisor-0.27.1/deploy/canary/000077500000000000000000000000001315410276000161635ustar00rootroot00000000000000cadvisor-0.27.1/deploy/canary/Dockerfile000066400000000000000000000005011315410276000201510ustar00rootroot00000000000000FROM golang:latest MAINTAINER vmarmol@google.com RUN apt-cache update && apt-get clean && apt-get install -y git dmsetup RUN git clone https://github.com/google/cadvisor.git /go/src/github.com/google/cadvisor RUN cd /go/src/github.com/google/cadvisor && make ENTRYPOINT ["/go/src/github.com/google/cadvisor/cadvisor"] cadvisor-0.27.1/devicemapper/000077500000000000000000000000001315410276000160565ustar00rootroot00000000000000cadvisor-0.27.1/devicemapper/dmsetup_client.go000066400000000000000000000043011315410276000214220ustar00rootroot00000000000000// Copyright 2016 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 devicemapper import ( "os/exec" "strconv" "strings" "github.com/golang/glog" ) // DmsetupClient is a low-level client for interacting with device mapper via // the `dmsetup` utility, which is provided by the `device-mapper` package. type DmsetupClient interface { // Table runs `dmsetup table` on the given device name and returns the // output or an error. Table(deviceName string) ([]byte, error) // Message runs `dmsetup message` on the given device, passing the given // message to the given sector, and returns the output or an error. Message(deviceName string, sector int, message string) ([]byte, error) // Status runs `dmsetup status` on the given device and returns the output // or an error. Status(deviceName string) ([]byte, error) } // NewDmSetupClient returns a new DmsetupClient. func NewDmsetupClient() DmsetupClient { return &defaultDmsetupClient{} } // defaultDmsetupClient is a functional DmsetupClient type defaultDmsetupClient struct{} var _ DmsetupClient = &defaultDmsetupClient{} func (c *defaultDmsetupClient) Table(deviceName string) ([]byte, error) { return c.dmsetup("table", deviceName) } func (c *defaultDmsetupClient) Message(deviceName string, sector int, message string) ([]byte, error) { return c.dmsetup("message", deviceName, strconv.Itoa(sector), message) } func (c *defaultDmsetupClient) Status(deviceName string) ([]byte, error) { return c.dmsetup("status", deviceName) } func (*defaultDmsetupClient) dmsetup(args ...string) ([]byte, error) { glog.V(5).Infof("running dmsetup %v", strings.Join(args, " ")) return exec.Command("dmsetup", args...).Output() } cadvisor-0.27.1/devicemapper/doc.go000066400000000000000000000012731315410276000171550ustar00rootroot00000000000000// Copyright 2016 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 devicemapper contains code for working with devicemapper package devicemapper cadvisor-0.27.1/devicemapper/fake/000077500000000000000000000000001315410276000167645ustar00rootroot00000000000000cadvisor-0.27.1/devicemapper/fake/dmsetup_client_fake.go000066400000000000000000000040151315410276000233200ustar00rootroot00000000000000// Copyright 2016 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 fake import ( "testing" ) type DmsetupCommand struct { Name string Result string Err error } // NewFakeDmsetupClient returns a new fake DmsetupClient. func NewFakeDmsetupClient(t *testing.T, commands ...DmsetupCommand) *FakeDmsetupClient { if len(commands) == 0 { commands = make([]DmsetupCommand, 0) } return &FakeDmsetupClient{t: t, commands: commands} } // FakeDmsetupClient is a thread-unsafe fake implementation of the // DmsetupClient interface type FakeDmsetupClient struct { t *testing.T commands []DmsetupCommand } func (c *FakeDmsetupClient) Table(deviceName string) ([]byte, error) { return c.dmsetup("table") } func (c *FakeDmsetupClient) Message(deviceName string, sector int, message string) ([]byte, error) { return c.dmsetup("message") } func (c *FakeDmsetupClient) Status(deviceName string) ([]byte, error) { return c.dmsetup("status") } func (c *FakeDmsetupClient) AddCommand(name string, result string, err error) { c.commands = append(c.commands, DmsetupCommand{name, result, err}) } func (c *FakeDmsetupClient) dmsetup(inputCommand string) ([]byte, error) { var nextCommand DmsetupCommand nextCommand, c.commands = c.commands[0], c.commands[1:] if nextCommand.Name != inputCommand { c.t.Fatalf("unexpected dmsetup command; expected: %q, got %q", nextCommand.Name, inputCommand) // should be unreachable in a test context. } return []byte(nextCommand.Result), nextCommand.Err } cadvisor-0.27.1/devicemapper/fake/thin_ls_client_fake.go000066400000000000000000000017311315410276000233010ustar00rootroot00000000000000// Copyright 2016 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 fake type FakeThinLsClient struct { result map[string]uint64 err error } // NewFakeThinLsClient returns a new fake ThinLsClient. func NewFakeThinLsClient(result map[string]uint64, err error) *FakeThinLsClient { return &FakeThinLsClient{result, err} } func (c *FakeThinLsClient) ThinLs(deviceName string) (map[string]uint64, error) { return c.result, c.err } cadvisor-0.27.1/devicemapper/thin_ls_client.go000066400000000000000000000051251315410276000214060ustar00rootroot00000000000000// Copyright 2016 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 devicemapper import ( "bufio" "bytes" "fmt" "os/exec" "strconv" "strings" "github.com/golang/glog" ) // thinLsClient knows how to run a thin_ls very specific to CoW usage for // containers. type thinLsClient interface { // ThinLs runs a thin ls on the given device, which is expected to be a // metadata device. The caller must hold the metadata snapshot for the // device. ThinLs(deviceName string) (map[string]uint64, error) } // newThinLsClient returns a thinLsClient or an error if the thin_ls binary // couldn't be located. func newThinLsClient() (thinLsClient, error) { thinLsPath, err := ThinLsBinaryPresent() if err != nil { return nil, fmt.Errorf("error creating thin_ls client: %v", err) } return &defaultThinLsClient{thinLsPath}, nil } // defaultThinLsClient is a functional thinLsClient type defaultThinLsClient struct { thinLsPath string } var _ thinLsClient = &defaultThinLsClient{} func (c *defaultThinLsClient) ThinLs(deviceName string) (map[string]uint64, error) { args := []string{"--no-headers", "-m", "-o", "DEV,EXCLUSIVE_BYTES", deviceName} glog.V(4).Infof("running command: thin_ls %v", strings.Join(args, " ")) output, err := exec.Command(c.thinLsPath, args...).Output() if err != nil { return nil, fmt.Errorf("Error running command `thin_ls %v`: %v\noutput:\n\n%v", strings.Join(args, " "), err, string(output)) } return parseThinLsOutput(output), nil } // parseThinLsOutput parses the output returned by thin_ls to build a map of // device id -> usage. func parseThinLsOutput(output []byte) map[string]uint64 { cache := map[string]uint64{} // parse output scanner := bufio.NewScanner(bytes.NewReader(output)) for scanner.Scan() { output := scanner.Text() fields := strings.Fields(output) if len(fields) != 2 { continue } deviceID := fields[0] usage, err := strconv.ParseUint(fields[1], 10, 64) if err != nil { glog.Warningf("unexpected error parsing thin_ls output: %v", err) continue } cache[deviceID] = usage } return cache } cadvisor-0.27.1/devicemapper/thin_ls_client_test.go000066400000000000000000000027451315410276000224520ustar00rootroot00000000000000// Copyright 2016 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 devicemapper import ( "reflect" "testing" ) func TestParseThinLsOutput(t *testing.T) { cases := []struct { name string input string expectedResult map[string]uint64 }{ { name: "ok", input: ` 1 2293760 2 2097152 3 131072 4 2031616`, expectedResult: map[string]uint64{ "1": 2293760, "2": 2097152, "3": 131072, "4": 2031616, }, }, { name: "skip bad rows", input: ` 1 2293760 2 2097152 3 131072ads 4d dsrv 2031616`, expectedResult: map[string]uint64{ "1": 2293760, "2": 2097152, }, }, } for _, tc := range cases { actualResult := parseThinLsOutput([]byte(tc.input)) if e, a := tc.expectedResult, actualResult; !reflect.DeepEqual(e, a) { t.Errorf("%v: unexpected result: expected %+v got %+v", tc.name, e, a) } } } cadvisor-0.27.1/devicemapper/thin_pool_watcher.go000066400000000000000000000125171315410276000221230ustar00rootroot00000000000000// Copyright 2016 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 devicemapper import ( "fmt" "strings" "sync" "time" "github.com/golang/glog" ) // ThinPoolWatcher maintains a cache of device name -> usage stats for a // devicemapper thin-pool using thin_ls. type ThinPoolWatcher struct { poolName string metadataDevice string lock *sync.RWMutex cache map[string]uint64 period time.Duration stopChan chan struct{} dmsetup DmsetupClient thinLsClient thinLsClient } // NewThinPoolWatcher returns a new ThinPoolWatcher for the given devicemapper // thin pool name and metadata device or an error. func NewThinPoolWatcher(poolName, metadataDevice string) (*ThinPoolWatcher, error) { thinLsClient, err := newThinLsClient() if err != nil { return nil, fmt.Errorf("encountered error creating thin_ls client: %v", err) } return &ThinPoolWatcher{poolName: poolName, metadataDevice: metadataDevice, lock: &sync.RWMutex{}, cache: make(map[string]uint64), period: 15 * time.Second, stopChan: make(chan struct{}), dmsetup: NewDmsetupClient(), thinLsClient: thinLsClient, }, nil } // Start starts the ThinPoolWatcher. func (w *ThinPoolWatcher) Start() { err := w.Refresh() if err != nil { glog.Errorf("encountered error refreshing thin pool watcher: %v", err) } for { select { case <-w.stopChan: return case <-time.After(w.period): start := time.Now() err = w.Refresh() if err != nil { glog.Errorf("encountered error refreshing thin pool watcher: %v", err) } // print latency for refresh duration := time.Since(start) glog.V(5).Infof("thin_ls(%d) took %s", start.Unix(), duration) } } } // Stop stops the ThinPoolWatcher. func (w *ThinPoolWatcher) Stop() { close(w.stopChan) } // GetUsage gets the cached usage value of the given device. func (w *ThinPoolWatcher) GetUsage(deviceId string) (uint64, error) { w.lock.RLock() defer w.lock.RUnlock() v, ok := w.cache[deviceId] if !ok { return 0, fmt.Errorf("no cached value for usage of device %v", deviceId) } return v, nil } const ( reserveMetadataMessage = "reserve_metadata_snap" releaseMetadataMessage = "release_metadata_snap" ) // Refresh performs a `thin_ls` of the pool being watched and refreshes the // cached data with the result. func (w *ThinPoolWatcher) Refresh() error { w.lock.Lock() defer w.lock.Unlock() currentlyReserved, err := w.checkReservation(w.poolName) if err != nil { err = fmt.Errorf("error determining whether snapshot is reserved: %v", err) return err } if currentlyReserved { glog.V(5).Infof("metadata for %v is currently reserved; releasing", w.poolName) _, err = w.dmsetup.Message(w.poolName, 0, releaseMetadataMessage) if err != nil { err = fmt.Errorf("error releasing metadata snapshot for %v: %v", w.poolName, err) return err } } glog.V(5).Infof("reserving metadata snapshot for thin-pool %v", w.poolName) // NOTE: "0" in the call below is for the 'sector' argument to 'dmsetup // message'. It's not needed for thin pools. if output, err := w.dmsetup.Message(w.poolName, 0, reserveMetadataMessage); err != nil { err = fmt.Errorf("error reserving metadata for thin-pool %v: %v output: %v", w.poolName, err, string(output)) return err } else { glog.V(5).Infof("reserved metadata snapshot for thin-pool %v", w.poolName) } defer func() { glog.V(5).Infof("releasing metadata snapshot for thin-pool %v", w.poolName) w.dmsetup.Message(w.poolName, 0, releaseMetadataMessage) }() glog.V(5).Infof("running thin_ls on metadata device %v", w.metadataDevice) newCache, err := w.thinLsClient.ThinLs(w.metadataDevice) if err != nil { err = fmt.Errorf("error performing thin_ls on metadata device %v: %v", w.metadataDevice, err) return err } w.cache = newCache return nil } const ( thinPoolDmsetupStatusHeldMetadataRoot = 6 thinPoolDmsetupStatusMinFields = thinPoolDmsetupStatusHeldMetadataRoot + 1 ) // checkReservation checks to see whether the thin device is currently holding // userspace metadata. func (w *ThinPoolWatcher) checkReservation(poolName string) (bool, error) { glog.V(5).Infof("checking whether the thin-pool is holding a metadata snapshot") output, err := w.dmsetup.Status(poolName) if err != nil { return false, err } // we care about the field at fields[thinPoolDmsetupStatusHeldMetadataRoot], // so make sure we get enough fields fields := strings.Fields(string(output)) if len(fields) < thinPoolDmsetupStatusMinFields { return false, fmt.Errorf("unexpected output of dmsetup status command; expected at least %d fields, got %v; output: %v", thinPoolDmsetupStatusMinFields, len(fields), string(output)) } heldMetadataRoot := fields[thinPoolDmsetupStatusHeldMetadataRoot] currentlyReserved := heldMetadataRoot != "-" return currentlyReserved, nil } cadvisor-0.27.1/devicemapper/thin_pool_watcher_test.go000066400000000000000000000161031315410276000231550ustar00rootroot00000000000000// Copyright 2016 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 devicemapper import ( "fmt" "sync" "testing" "time" "github.com/google/cadvisor/devicemapper/fake" ) func TestRefresh(t *testing.T) { usage := map[string]uint64{ "1": 12345, "2": 23456, "3": 34567, } cases := []struct { name string dmsetupCommands []fake.DmsetupCommand thinLsOutput map[string]uint64 thinLsErr error expectedError bool deviceId string expectedUsage uint64 }{ { name: "check reservation fails", dmsetupCommands: []fake.DmsetupCommand{ {Name: "status", Result: "", Err: fmt.Errorf("not gonna work")}, }, expectedError: true, }, { name: "no existing reservation - ok with minimum # of fields", dmsetupCommands: []fake.DmsetupCommand{ {Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 -", Err: nil}, // status check {Name: "message", Result: "", Err: nil}, // make reservation {Name: "message", Result: "", Err: nil}, // release reservation }, thinLsOutput: usage, expectedError: false, deviceId: "2", expectedUsage: 23456, }, { name: "no existing reservation - ok", dmsetupCommands: []fake.DmsetupCommand{ {Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 - rw no_discard_passdown error_if_no_space - ", Err: nil}, // status check {Name: "message", Result: "", Err: nil}, // make reservation {Name: "message", Result: "", Err: nil}, // release reservation }, thinLsOutput: usage, expectedError: false, deviceId: "2", expectedUsage: 23456, }, { name: "existing reservation - ok", dmsetupCommands: []fake.DmsetupCommand{ // status check {Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 39 rw no_discard_passdown error_if_no_space - ", Err: nil}, // release reservation {Name: "message", Result: "", Err: nil}, // make reservation {Name: "message", Result: "", Err: nil}, // release reservation {Name: "message", Result: "", Err: nil}, }, thinLsOutput: usage, expectedError: false, deviceId: "3", expectedUsage: 34567, }, { name: "failure releasing existing reservation", dmsetupCommands: []fake.DmsetupCommand{ // status check {Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 39 rw no_discard_passdown error_if_no_space - ", Err: nil}, // release reservation {Name: "message", Result: "", Err: fmt.Errorf("not gonna work")}, }, expectedError: true, }, { name: "failure making reservation", dmsetupCommands: []fake.DmsetupCommand{ // status check {Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 39 rw no_discard_passdown error_if_no_space - ", Err: nil}, // release reservation {Name: "message", Result: "", Err: nil}, // make reservation {Name: "message", Result: "", Err: fmt.Errorf("not gonna work")}, }, expectedError: true, }, { name: "failure running thin_ls", dmsetupCommands: []fake.DmsetupCommand{ // status check {Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 39 rw no_discard_passdown error_if_no_space - ", Err: nil}, // release reservation {Name: "message", Result: "", Err: nil}, // make reservation {Name: "message", Result: "", Err: nil}, // release reservation {Name: "message", Result: "", Err: nil}, }, thinLsErr: fmt.Errorf("not gonna work"), expectedError: true, }, } for _, tc := range cases { dmsetup := fake.NewFakeDmsetupClient(t, tc.dmsetupCommands...) thinLsClient := fake.NewFakeThinLsClient(tc.thinLsOutput, tc.thinLsErr) watcher := &ThinPoolWatcher{ poolName: "test pool name", metadataDevice: "/dev/mapper/metadata-device", lock: &sync.RWMutex{}, period: 15 * time.Second, stopChan: make(chan struct{}), dmsetup: dmsetup, thinLsClient: thinLsClient, } err := watcher.Refresh() if err != nil { if !tc.expectedError { t.Errorf("%v: unexpected error: %v", tc.name, err) } continue } else if tc.expectedError { t.Errorf("%v: unexpected success", tc.name) continue } actualUsage, err := watcher.GetUsage(tc.deviceId) if err != nil { t.Errorf("%v: device ID not found: %v", tc.deviceId, err) continue } if e, a := tc.expectedUsage, actualUsage; e != a { t.Errorf("%v: actual usage did not match expected usage: expected: %v got: %v", tc.name, e, a) } } } func TestCheckReservation(t *testing.T) { cases := []struct { name string statusResult string statusErr error expectedResult bool expectedErr error }{ { name: "existing reservation 1", statusResult: "0 75497472 thin-pool 65 327/524288 14092/589824 36 rw no_discard_passdown queue_if_no_space - ", expectedResult: true, }, { name: "existing reservation 2", statusResult: "0 12345 thin-pool 65 327/45678 14092/45678 36 rw discard_passdown error_if_no_space needs_check ", expectedResult: true, }, { name: "no reservation 1", statusResult: "0 75497472 thin-pool 65 327/524288 14092/589824 - rw no_discard_passdown error_if_no_space - ", expectedResult: false, }, { name: "no reservation 2", statusResult: "0 75 thin-pool 65 327/12345 14092/589824 - rw no_discard_passdown queue_if_no_space - ", expectedResult: false, }, { name: "no reservation 2", statusResult: "0 75 thin-pool 65 327/12345 14092/589824 - rw no_discard_passdown queue_if_no_space - ", expectedResult: false, }, } for _, tc := range cases { fakeDmsetupClient := fake.NewFakeDmsetupClient(t) fakeDmsetupClient.AddCommand("status", tc.statusResult, tc.statusErr) watcher := &ThinPoolWatcher{dmsetup: fakeDmsetupClient} actualResult, err := watcher.checkReservation("test pool") if err != nil { if tc.expectedErr == nil { t.Errorf("%v: unexpected error running checkReservation: %v", tc.name, err) } } else if tc.expectedErr != nil { t.Errorf("%v: unexpected success running checkReservation", tc.name) } if e, a := tc.expectedResult, actualResult; e != a { t.Errorf("%v: unexpected result from checkReservation: expected: %v got: %v", tc.name, e, a) } } } cadvisor-0.27.1/devicemapper/util.go000066400000000000000000000026341315410276000173670ustar00rootroot00000000000000// Copyright 2016 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 devicemapper import ( "fmt" "os" "path/filepath" ) // ThinLsBinaryPresent returns the location of the thin_ls binary in the mount // namespace cadvisor is running in or an error. The locations checked are: // // - /sbin/ // - /bin/ // - /usr/sbin/ // - /usr/bin/ // // The thin_ls binary is provided by the device-mapper-persistent-data // package. func ThinLsBinaryPresent() (string, error) { var ( thinLsPath string err error ) for _, path := range []string{"/sbin", "/bin", "/usr/sbin/", "/usr/bin"} { // try paths for non-containerized operation // note: thin_ls is most likely a symlink to pdata_tools thinLsPath = filepath.Join(path, "thin_ls") _, err = os.Stat(thinLsPath) if err == nil { return thinLsPath, nil } } return "", fmt.Errorf("unable to find thin_ls binary") } cadvisor-0.27.1/docs/000077500000000000000000000000001315410276000143425ustar00rootroot00000000000000cadvisor-0.27.1/docs/api.md000066400000000000000000000123401315410276000154350ustar00rootroot00000000000000# cAdvisor Remote REST API cAdvisor exposes its raw and processed stats via a versioned remote REST API: `http://:/api//` The current version of the API is `v1.3`. There is a beta release of the `v2.0` API [available](api_v2.md). ## Version 1.3 This version exposes the same endpoints as `v1.2` with one additional read-only endpoint. ### Events The resource name for Docker container information is as follows: `/api/v1.3/events/` Querying the endpoint receives a list of events which are a serialized `Event` JSON objects (found in [info/v1/container.go](../info/v1/container.go)). The endpoint accepts a certain number of query parameters: | Parameter | Description | Default | |-------------------|--------------------------------------------------------------------------------|-------------------| | `start_time` | Start time of events to query (for stream=false) | Beginning of time | | `end_time` | End time of events to query (for stream=false) | Now | | `stream` | Whether to stream new events as they occur. If false returns historical events | false | | `subcontainers` | Whether to also return events for all subcontainers | false | | `max_events` | The max number of events to return (for stream=false) | 10 | | `all_events` | Whether to include all supported event types | false | | `oom_events` | Whether to include OOM events | false | | `oom_kill_events` | Whether to include OOM kill events | false | | `creation_events` | Whether to include container creation events | false | | `deletion_events` | Whether to include container deletion events | false | ## Version 1.2 This version exposes the same endpoints as `v1.1` with one additional read-only endpoint. ### Docker Container Information The resource name for Docker container information is as follows: `/api/v1.2/docker/` The Docker name can be either the UUID or the short name of the container. It returns the information of the specified container(s). The information is returned as a list of serialized `ContainerInfo` JSON objects (found in [info/v1/container.go](../info/v1/container.go)). ## Version 1.1 This version exposes the same endpoints as `v1.0` with one additional read-only endpoint. ### Subcontainer Information The resource name for subcontainer information is as follows: `/api/v1.1/subcontainers/` Where the absolute container name follows the lmctfy naming convention (described bellow). It returns the information of the specified container and all subcontainers (recursively). The information is returned as a list of serialized `ContainerInfo` JSON objects (found in [info/v1/container.go](../info/v1/container.go)). ## 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/v1/container.go](../info/v1/container.go) ### Machine Information The resource name for machine information is as follows: `/api/vX.Y/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) - Maximum supported CPU frequency (in kHz) - Available filesystems: major, minor numbers and capacity (in bytes) - Network devices: mac addresses, MTU, and speed (if available) - Machine topology: Nodes, cores, threads, per-node memory, and caches The actual object is the marshalled JSON of the `MachineInfo` struct found in [info/v1/machine.go](../info/v1/machine.go) cadvisor-0.27.1/docs/api_v2.md000066400000000000000000000116201315410276000160440ustar00rootroot00000000000000# cAdvisor Remote REST API cAdvisor exposes its raw and processed stats via a versioned remote REST API: `http://:/api//` This document covers the detail of version 2.0. All resources covered in this version are read-only. NOTE: v2.0 is still a work in progress. ## Version information Software version for cAdvisor can be obtained from version endpoint as follows: `/api/v2.0/version` ## Machine Information The resource name for machine information is as follows: `/api/v2.0/machine` The machine information is returned as a JSON object of the `MachineInfo` struct found in [info/v1/machine.go](../info/v1/machine.go) ## Attributes Attributes endpoint provides hardware and software attributes of the running machine. The resource name for attributes is: `/api/v2.0/attributes` Hardware information includes all information covered by machine endpoint. Software information include version of cAdvisor, kernel, docker, and underlying OS. The actual object is the marshalled JSON of the `Attributes` struct found in [info/v2/machine.go](../info/v2/machine.go) ## Container Stats The resource name for container stats information is: `/api/v2.0/stats/` ### Stats request options Stats support following options in the request: - `type`: describes the type of identifier. Supported values are `name`(default) and `docker`. `name` implies that the identifier is an absolute container name. `docker` implies that the identifier is a docker id. - `recursive`: Option to specify if stats for subcontainers of the requested containers should also be reported. Default is false. - `count`: Number of stats samples to be reported. Default is 64. ### Container name When container identifier is of type `name`, the identifier is interpreted as the absolute container name. Naming follows the lmctfy convention. For example: | Container Name | Resource Name | |----------------------|-------------------------------------------| | / | /api/v2.0/containers/ | | /foo | /api/v2.0/containers/foo | | /docker/2c4dee605d22 | /api/v2.0/containers/docker/2c4dee605d22 | Note that the root container (`/`) contains usage for the entire machine. All Docker containers are listed under `/docker`. Also, `type=name` is not required in the examples above as `name` is the default type. ### Docker Containers When container identifier is of type `docker`, the identifier is interpreted as docker id. For example: | Docker container | Resource Name | |----------------------|-------------------------------------------| | All docker containers| /api/v2.0/stats?type=docker&recursive=true| | clever_colden | /api/v2.0/stats/clever_colden?type=docker | | 2c4dee605d22 | /api/v2.0/stats/2c4dee605d22?type=docker | The Docker name can be either the UUID or the short name of the container. It returns the information of the specified container(s). Note that `recursive` is only valid when docker root is specified. It is used to get stats for all docker containers. ### Returned stats The stats information is returned as a JSON object containing a map from container name to list of stat objects. Stat object is the marshalled JSON of the `ContainerStats` struct found in [info/v2/container.go](../info/v2/container.go) ## Container Stats Summary Instead of a list of periodically collected detailed samples, cAdvisor can also provide a summary of stats for a container. It provides the latest collected stats and percentiles (max, average, and 90%ile) values for usage in last minute and hour. (Usage summary for last day exists, but is not currently used.) Unlike the regular stats API, only selected resources are captured by `summary`. Currently it is limited to cpu and memory usage. The resource name for container summary information is: `/api/v2.0/summary/` Additionally, `type` and `recursive` options can be used to describe the identifier type and ask for summary of all subcontainers respectively. The semantics are same as described for container stats above. The returned summary information is a JSON object containing a map from container name to list of summary objects. Summary object is the marshalled JSON of the `DerivedStats` struct found in [info/v2/container.go](../info/v2/container.go) ## Container Spec The resource name for container stats information is: `/api/v2.0/spec/` Additionally, `type` and `recursive` options can be used to describe the identifier type and ask for spec of all subcontainers respectively. The semantics are same as described for container stats above. The spec information is returned as a JSON object containing a map from container name to list of spec objects. Spec object is the marshalled JSON of the `ContainerSpec` struct found in [info/v2/container.go](../info/v2/container.go) cadvisor-0.27.1/docs/application_metrics.md000066400000000000000000000117501315410276000207210ustar00rootroot00000000000000# Collecting Application Metrics with cAdvisor **Note** Application metrics support is in Alpha. We are still making a bunch of interface changes. ## Introduction In addition to usage metrics, cAdvisor can also be configured to collect application metrics. A container can expose application metrics through multiple ways - on a status page, through structured info like prometheus, or have a separate API for fetching stats. cAdvisor provides a generic way to collect these metrics. Additional templates are provided to automate some well-known collection profiles. ## Specifying application metrics Application metrics specification consists of two steps: * Creating a configuration * Passing the configuration location to cadvisor ## Creating a configuration An application metric configuration tells cAdvisor where to look for application metrics and specifies other parameters about how to export the metrics from cAdvisor to UI and backends. The metric config includes: * Endpoint (Location to collect metrics from) * Name of metric * Type (Counter, Gauge, ...) * Data Type (int, float) * Units (kbps, seconds, count) * Polling Frequency * Regexps (Regular expressions to specify which metrics to collect and how to parse them) Here is an example of a very generic metric collector that assumes no structured information: ``` { "endpoint" : "http://localhost:8000/nginx_status", "metrics_config" : [ { "name" : "activeConnections", "metric_type" : "gauge", "units" : "number of active connections", "data_type" : "int", "polling_frequency" : 10, "regex" : "Active connections: ([0-9]+)" }, { "name" : "reading", "metric_type" : "gauge", "units" : "number of reading connections", "data_type" : "int", "polling_frequency" : 10, "regex" : "Reading: ([0-9]+) .*" } ] } ``` For structured metrics export, eg. Prometheus, the config can shrink down to just the endpoint, as other information can be gleaned from the structure. Here is a sample prometheus config that collects all metrics from an endpoint. ``` { "endpoint" : "http://localhost:9100/metrics" } ``` Another sample config that collects only selected metrics: ``` { "endpoint" : "http://localhost:8000/metrics", "metrics_config" : [ "scheduler_binding_latency", "scheduler_e2e_scheduling_latency", "scheduling_algorithm_latency" ] } ``` ## Passing the configuration to cAdvisor cAdvisor can discover any configurations for a container using Docker container labels. Any label starting with ```io.cadvisor.metric``` is parsed as a cadvisor application-metric label. cAdvisor uses the value as an indicator of where the configuration can be found. Labels of the form ```io.cadvisor.metric.prometheus-xyz``` indicate that the configuration points to a Prometheus metrics endpoint. The configuration file can either be part of the container image or can be added on at runtime with a volume. This makes sure that there is no connection between the host where the container is running and the application metrics configuration. A container is self-contained for its metric information. So a sample configuration for redis would look like: Dockerfile (or runtime): ``` FROM redis ADD redis_config.json /var/cadvisor/redis_config.json LABEL io.cadvisor.metric.redis="/var/cadvisor/redis_config.json" ``` cAdvisor will then reach into the container image at runtime, process the config, and start collecting and exposing application metrics. Note that cAdvisor specifically looks at the container labels to extract this information. In Docker 1.8, containers don't inherit labels from their images, and thus you must specify the label at runtime. ## API access to application-specific metrics A new endpoint is added for collecting application-specific metrics for a particular container: ``` http://localhost:8080/api/v2.0/appmetrics/containerName ``` The set of application-metrics being collected can be discovered from the container spec: ``` http://localhost:8080/api/v2.0/spec/containerName ``` Regular stats API also has application-metrics appended to it: ``` http://localhost:8080/api/v2.0/stats/containerName ``` ## UI changes Application-metrics show up on the container page after the resource metrics. ## Ongoing work ### Templates Next step for application-metrics is to add templates for well-known containers that have stable stats API. These would be specified by a new label ```io.cadvisor.metric.type```. If the label value is a known type, cAdvisor would start collecting stats automatically without needing any further config. Config can still be used to override any specific parameters - like set of metrics to collect. ### UI enhancements There are a bunch of UI enhancements under way: * Better handling/display of metrics - eg. allowing overlaying metrics on the same graphs, handling metric types like percentiles. * Moving application metrics to separate tab. * Adding control to show only selected metrics on UI while still exporting everything through the API. cadvisor-0.27.1/docs/clients.md000066400000000000000000000007031315410276000163250ustar00rootroot00000000000000# cAdvisor API Clients There is an official Go client implementation in the [client](../client/) directory. You can use it on your own Go project by including it like this: ```go import "github.com/google/cadvisor/client" client, err = client.NewClient("http://localhost:8080/") mInfo, err := client.MachineInfo() ``` Do you know of another cAdvisor client? Maybe in another language? Please let us know! We'd be happy to add a note on this page. cadvisor-0.27.1/docs/deploy.md000066400000000000000000000013241315410276000161600ustar00rootroot00000000000000# Building and Deploying the cAdvisor Docker Container ## Building Building the cAdvisor Docker container is simple, just run: ``` $ ./deploy/build.sh ``` Which will statically build the cAdvisor binary and then build the Docker image. The resulting Docker image will be called `google/cadvisor:canary`. This image is very bare, containing the cAdvisor binary and nothing else. ## Deploying All cAdvisor releases are tagged and correspond to a Docker image. The latest supported release uses the `latest` tag. We have a `beta` and `canary` tag for pre-release versions with newer features. You can see more details about this in the cAdvisor Docker [registry](https://registry.hub.docker.com/u/google/cadvisor/) page. cadvisor-0.27.1/docs/development/000077500000000000000000000000001315410276000166645ustar00rootroot00000000000000cadvisor-0.27.1/docs/development/README.md000066400000000000000000000003531315410276000201440ustar00rootroot00000000000000The [development](./) directory holds documentation for cAdvisor developers and contributors. If you are looking for development using cAdvisor (as opposed to development of cAdvisor), then these documents probably don't apply to you. cadvisor-0.27.1/docs/development/build.md000066400000000000000000000022141315410276000203040ustar00rootroot00000000000000# Building and Testing cAdvisor **Note**: cAdvisor only builds on Linux since it uses Linux-only APIs. ## Installing Dependencies cAdvisor is written in the [Go](http://golang.org) programming language. If you haven't set up a Go development environment, please follow [these instructions](http://golang.org/doc/code.html) to install go tool and set up GOPATH. Note that the version of Go in package repositories of some operating systems is outdated, so please [download](https://golang.org/dl/) the latest version. **Note**: cAdvisor requires Go 1.6 to build. After setting up Go, you should be able to `go get` cAdvisor as expected (we use `-d` to only download): ``` $ go get -d github.com/google/cadvisor ``` ## Building from Source At this point you can build cAdvisor from the source folder: ``` $GOPATH/src/github.com/google/cadvisor $ make build ``` or run only unit tests: ``` $GOPATH/src/github.com/google/cadvisor $ make test ``` For integration tests, see the [integration testing](integration_testing.md) page. ## Running Built Binary Now you can run the built binary: ``` $GOPATH/src/github.com/google/cadvisor $ sudo ./cadvisor ``` cadvisor-0.27.1/docs/development/integration_testing.md000066400000000000000000000012601315410276000232650ustar00rootroot00000000000000# Integration Testing cAdvisor The cAdvisor integration tests can be found in `integration/tests`. These run queries on a running cAdvisor. To run these tests: ``` $ go run integration/runner/runner.go -port=PORT ``` This will build a cAdvisor from the current repository and start it on the target machine before running the tests. To simply run the tests against an existing cAdvisor: ``` $ go test github.com/google/cadvisor/integration/tests/... -host=HOST -port=PORT ``` Note that `HOST` and `PORT` default to `localhost` and `8080` respectively. Today We only support remote execution in Google Compute Engine since that is where we run our continuous builds. cadvisor-0.27.1/docs/development/issues.md000066400000000000000000000040301315410276000205160ustar00rootroot00000000000000# GitHub Issue tracking cAdvisor This document outlines the process around GitHub issue tracking for cAdvisor at https://github.com/google/cadvisor/issues ## Labels A brief explanation of what issue labels mean. Most labels also apply to pull requests, but for pull requests which reference an issue, it is not necessary to copy the same labels to the PR. - `area/API` - For issues related to the API. - `area/UI` - For issues related to the web UI. - `area/documentation` - For issues related to the documentation (inline comments or markdown). - `area/performance` - For issues related to cAdvisor performance (speed, memory, etc.). - `area/storage` - For issues related to cAdvisor storage plugins. - `area/testing` - For issues related to testing (integration tests, unit tests, jenkins, etc.) - `closed/duplicate` - For issues which have been closed as duplicates of another issue. The final comment on the issue should hold a reference the duplicate issue. - `closed/infeasible` - For issues which cannot be resolved (e.g. a request for a feature we cannot or do not want to add). - `community-assigned` - For issues which are being worked on by a community member (when github won't let us assign the issue to them). - `kind/bug` - For issues referring to a bug in the existing implementation. - `kind/enhancement` - For issues proposing an enhancement or new feature. - `kind/support` - For issues which might just be user confusion / environment setup. If support issue ends up requiring a PR, it should probably be relabeled (for example, to `bug`). Many support issues may indicate a shortcoming of the documentation. - `help wanted` - For issues which have been highlighted as a good place to contribute to cAdvisor. `help wanted` issues could be enhancements that the core team is unlikely to get to in the near future, or small projects which might be a good starting point. Lack of a `help wanted` label does not mean we won't accept contributions, it only means it was not identified as a candidate project for community contributions. cadvisor-0.27.1/docs/development/releasing.md000066400000000000000000000061221315410276000211600ustar00rootroot00000000000000# cAdvisor Release Instructions Google internal-only version: [cAdvisor Release Instructions](http://go/cadvisor-release-instructions) ## 1. Send Release PR Example: https://github.com/google/cadvisor/pull/1281 Add release notes to [CHANGELOG.md](../../CHANGELOG.md) - Tip: Use a github PR search to find changes since the last release `is:pr is:merged merged:>2016-04-21` ## 2. Create the release tag ### 2.a Create the release branch (only for major/minor releases) Skip this step for patch releases. ``` # Sync to HEAD, or the commit to branch at $ git fetch upstream && git checkout upstream/master # Create the branch $ git branch release-v0.XX # Push it to upstream $ git push git@github.com:google/cadvisor.git release-v0.XX ``` ### 2.b Tag the release For a release of minor version XX, patch version YY: ``` # Checkout the release branch $ git fetch upstream && git checkout upstream/release-v0.XX # Tag the release commit. If you aren't signing, ommit the -s $ git tag -s -a v0.XX.YY # Push it to upstream $ git push git@github.com:google/cadvisor.git v0.XX.YY ``` ## 3. Build release binary Command: `make release` - Make sure your git client is synced to the release cut point - Try to build it from the release branch, since we include that in the binary version - Verify the ldflags output, in particular check the Version, BuildUser, and GoVersion are expected Once the build is complete, check the VERSION and note the sha256 hash. ## 4. Push the Docker images Docker Hub: ``` $ docker login Username: **** Password: **** $ docker push google/cadvisor:$VERSION $ docker logout # Good practice with shared account ``` Google Container Registry: ``` $ gcloud auth login ... Go to the following link in your browser: https://accounts.google.com/o/oauth2/auth? Enter verification code: **** $ gcloud docker push gcr.io/google_containers/cadvisor:$VERSION $ gcloud auth revoke # Log out of shared account ``` ## 5. Cut the release Go to https://github.com/google/cadvisor/releases and click "Draft a new release" - "Tag version" and "Release title" should be preceded by 'v' and then the version. Select the tag pushed in step 2.b - Copy an old release as a template (e.g. github.com/google/cadvisor/releases/tag/v0.23.1) - Body should start with release notes (from CHANGELOG.md) - Next is the Docker image: `google/cadvisor:$VERSION` - Next are the binary hashes (from step 3) - Upload the binary build in step 3 - If this is an alpha or beta release, mark the release as a "pre-release" - Click publish when done ## 6. Finalize the release Once you are satisfied with the release quality (consider waiting a week for bug reports to come in), it is time to promote the release to *latest* 1. Edit the github release a final time, and uncheck the "Pre-release" checkbox 2. Tag the docker & gcr.io releases with the latest version ``` $ docker pull google/cadvisor:$VERSION $ docker tag -f google/cadvisor:$VERSION google/cadvisor:latest $ docker tag -f google/cadvisor:$VERSION gcr.io/google_containers/cadvisor:latest ``` 3. Repeat steps 4.a and 4.b to push the image tagged with latest cadvisor-0.27.1/docs/running.md000066400000000000000000000066711315410276000163560ustar00rootroot00000000000000# Running cAdvisor ## With Docker We have a Docker image that includes everything you need to get started. Simply run: ``` sudo docker run \ --volume=/:/rootfs:ro \ --volume=/var/run:/var/run:rw \ --volume=/sys:/sys:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \ --publish=8080:8080 \ --detach=true \ --name=cadvisor \ google/cadvisor:latest ``` cAdvisor is now running (in the background) on `http://localhost:8080/`. The setup includes directories with Docker state cAdvisor needs to observe. **Note**: If docker daemon is running with [user namespace enabled](https://docs.docker.com/engine/reference/commandline/dockerd/#starting-the-daemon-with-user-namespaces-enabled), You need to add `--userns=host` option in order for cAdvisor to monitor Docker containers, otherwise cAdvisor can not connect to docker daemon. ## Latest Canary The latest cAdvisor canary release is continuously built from HEAD and available as an Automated Build Docker image: [google/cadvisor-canary](https://registry.hub.docker.com/u/google/cadvisor-canary/). We do *not* recommend using this image in production due to its large size and volatility. ## With Boot2Docker After booting up a boot2docker instance, run cAdvisor image with the same docker command mentioned above. cAdvisor can now be accessed at port 8080 of your boot2docker instance. The host IP can be found through DOCKER_HOST environment variable setup by boot2docker: ``` $ echo $DOCKER_HOST tcp://192.168.59.103:2376 ``` In this case, cAdvisor UI should be accessible on `http://192.168.59.103:8080` ## Other Configurations ### CentOS, Fedora, and RHEL You may need to run the container with `--privileged=true` and `--volume=/cgroup:/cgroup:ro \` in order for cAdvisor to monitor Docker containers. RHEL and CentOS lock down their containers a bit more. cAdvisor needs access to the Docker daemon through its socket. This requires `--privileged=true` in RHEL and CentOS. On some versions of RHEL and CentOS the cgroup hierarchies are mounted in `/cgroup` so run cAdvisor with an additional Docker option of `--volume=/cgroup:/cgroup:ro \`. ### Debian By default, Debian disables the memory cgroup which does not allow cAdvisor to gather memory stats. To enable the memory cgroup take a look at [these instructions](https://github.com/google/cadvisor/issues/432). ### LXC Docker exec driver If you are using Docker with the LXC exec driver, then you need to manually specify all cgroup mounts by adding the: ``` --volume=/cgroup/cpu:/cgroup/cpu \ --volume=/cgroup/cpuacct:/cgroup/cpuacct \ --volume=/cgroup/cpuset:/cgroup/cpuset \ --volume=/cgroup/memory:/cgroup/memory \ --volume=/cgroup/blkio:/cgroup/blkio \ ``` ### Invalid Bindmount `/` This is a problem seen in older versions of Docker. To fix, start cAdvisor without the `--volume=/:/rootfs:ro` mount. cAdvisor will degrade gracefully by dropping stats that depend on access to the machine root. ## Standalone cAdvisor is a static Go binary with no external dependencies. To run it standalone all you should need to do is run it! Note that some data sources may require root priviledges. cAdvisor will gracefully degrade its features to those it can expose with the access given. ``` $ sudo cadvisor ``` cAdvisor is now running (in the foreground) on `http://localhost:8080/`. ## Runtime Options cAdvisor has a series of flags that can be used to configure its runtime behavior. More details can be found in runtime [options](runtime_options.md). cadvisor-0.27.1/docs/runtime_options.md000066400000000000000000000143721315410276000201310ustar00rootroot00000000000000# cAdvisor Runtime Options This document describes a set of runtime flags available in cAdvisor. ## Container Hints Container hints are a way to pass extra information about a container to cAdvisor. In this way cAdvisor can augment the stats it gathers. For more information on the container hints format see its [definition](../container/common/container_hints.go). Note that container hints are only used by the raw container driver today. ``` --container_hints="/etc/cadvisor/container_hints.json": location of the container hints file ``` ## CPU ``` --enable_load_reader=false: Whether to enable cpu load reader --max_procs=0: max number of CPUs that can be used simultaneously. Less than 1 for default (number of cores). ``` ## Debugging and Logging cAdvisor-native flags that help in debugging: ``` --log_backtrace_at="": when logging hits line file:N, emit a stack trace --log_cadvisor_usage=false: Whether to log the usage of the cAdvisor container --version=false: print cAdvisor version and exit --profiling=false: Enable profiling via web interface host:port/debug/pprof/ ``` From [glog](https://github.com/golang/glog) here are some flags we find useful: ``` --log_dir="": If non-empty, write log files in this directory --logtostderr=false: log to standard error instead of files --alsologtostderr=false: log to standard error as well as files --stderrthreshold=0: logs at or above this threshold go to stderr --v=0: log level for V logs --vmodule=: comma-separated list of pattern=N settings for file-filtered logging ``` ## Docker ``` --docker="unix:///var/run/docker.sock": docker endpoint (default "unix:///var/run/docker.sock") --docker_env_metadata_whitelist="": a comma-separated list of environment variable keys that needs to be collected for docker containers --docker_only=false: Only report docker containers in addition to root stats --docker_root="/var/lib/docker": DEPRECATED: docker root is read from docker info (this is a fallback, default: /var/lib/docker) (default "/var/lib/docker") --docker-tls: use TLS to connect to docker --docker-tls-cert="cert.pem": client certificate for TLS-connection with docker --docker-tls-key="key.pem": private key for TLS-connection with docker --docker-tls-ca="ca.pem": trusted CA for TLS-connection with docker ``` ## Housekeeping Housekeeping is the periodic actions cAdvisor takes. During these actions, cAdvisor will gather container stats. These flags control how and when cAdvisor performs housekeeping. #### Dynamic Housekeeping Dynamic housekeeping intervals let cAdvisor vary how often it gathers stats. It does this depending on how active the container is. Turning this off provides predictable housekeeping intervals, but increases the resource usage of cAdvisor. ``` --allow_dynamic_housekeeping=true: Whether to allow the housekeeping interval to be dynamic ``` #### Housekeeping Intervals Intervals for housekeeping. cAdvisor has two housekeepings: global and per-container. Global housekeeping is a singular housekeeping done once in cAdvisor. This typically does detection of new containers. Today, cAdvisor discovers new containers with kernel events so this global housekeeping is mostly used as backup in the case that there are any missed events. Per-container housekeeping is run once on each container cAdvisor tracks. This typically gets container stats. ``` --global_housekeeping_interval=1m0s: Interval between global housekeepings --housekeeping_interval=1s: Interval between container housekeepings --max_housekeeping_interval=1m0s: Largest interval to allow between container housekeepings (default 1m0s) ``` ## HTTP Specify where cAdvisor listens. ``` --http_auth_file="": HTTP auth file for the web UI --http_auth_realm="localhost": HTTP auth realm for the web UI (default "localhost") --http_digest_file="": HTTP digest file for the web UI --http_digest_realm="localhost": HTTP digest file for the web UI (default "localhost") --listen_ip="": IP to listen on, defaults to all IPs --port=8080: port to listen (default 8080) ``` ## Local Storage Duration cAdvisor stores the latest historical data in memory. How long of a history it stores can be configured with the `--storage_duration` flag. ``` --storage_duration=2m0s: How long to store data. ``` ## Machine ``` --boot_id_file="/proc/sys/kernel/random/boot_id": Comma-separated list of files to check for boot-id. Use the first one that exists. (default "/proc/sys/kernel/random/boot_id") --machine_id_file="/etc/machine-id,/var/lib/dbus/machine-id": Comma-separated list of files to check for machine-id. Use the first one that exists. (default "/etc/machine-id,/var/lib/dbus/machine-id") ``` ## Metrics ``` --application_metrics_count_limit=100: Max number of application metrics to store (per container) (default 100) --collector_cert="": Collector's certificate, exposed to endpoints for certificate based authentication. --collector_key="": Key for the collector's certificate --disable_metrics=tcp, udp: comma-separated list of metrics to be disabled. Options are 'disk', 'network', 'tcp', 'udp'. Note: tcp and udp are disabled by default due to high CPU usage. (default tcp,udp) --prometheus_endpoint="/metrics": Endpoint to expose Prometheus metrics on (default "/metrics") ``` ## Storage Drivers ``` --storage_driver="": Storage driver to use. Data is always cached shortly in memory, this controls where data is pushed besides the local cache. Empty means none. Options are: , bigquery, elasticsearch, influxdb, kafka, redis, statsd, stdout --storage_driver_buffer_duration="1m0s": Writes in the storage driver will be buffered for this duration, and committed to the non memory backends as a single transaction (default 1m0s) --storage_driver_db="cadvisor": database name (default "cadvisor") --storage_driver_host="localhost:8086": database host:port (default "localhost:8086") --storage_driver_password="root": database password (default "root") --storage_driver_secure=false: use secure connection with database --storage_driver_table="stats": table name (default "stats") --storage_driver_user="root": database username (default "root") ``` For storage driver specific instructions: * [InfluxDB instructions](storage/influxdb.md). * [ElasticSearch instructions](storage/elasticsearch.md). * [Kafka instructions](storage/kafka.md). * [Prometheus instructions](storage/prometheus.md). cadvisor-0.27.1/docs/storage/000077500000000000000000000000001315410276000160065ustar00rootroot00000000000000cadvisor-0.27.1/docs/storage/README.md000066400000000000000000000014451315410276000172710ustar00rootroot00000000000000# cAdvisor Storage Plugins cAdvisor supports exporting stats to various storage driver plugins. To enable a storage driver, set the `-storage_driver` flag. ## Storage drivers - [BigQuery](https://cloud.google.com/bigquery/). See the [documentation](../../storage/bigquery/README.md) for usage. - [ElasticSearch](https://www.elastic.co/). See the [documentation](elasticsearch.md) for usage and examples. - [InfluxDB](https://influxdb.com/). See the [documentation](influxdb.md) for usage and examples. - [Kafka](http://kafka.apache.org/). See the [documentation](kafka.md) for usage. - [Prometheus](https://prometheus.io). See the [documentation](prometheus.md) for usage and examples. - [Redis](http://redis.io/) - [StatsD](https://github.com/etsy/statsd) - `stdout` - write stats to standard output. cadvisor-0.27.1/docs/storage/elasticsearch.md000066400000000000000000000013711315410276000211440ustar00rootroot00000000000000# Exporting cAdvisor Stats to ElasticSearch cAdvisor supports exporting stats to [ElasticSearch](https://www.elastic.co/). To use ES, you need to provide the additional flags to cAdvisor: Set the storage driver as ES: ``` -storage_driver=elasticsearch ``` Specify ES host address: ``` -storage_driver_es_host="http://elasticsearch:9200" ``` There are also optional flags: ``` # ElasticSearch type name. By default it's "stats". -storage_driver_es_type="stats" # ElasticSearch can use a sniffing process to find all nodes of your cluster automatically. False by default. -storage_driver_es_enable_sniffer=false ``` # Examples For a detailed tutorial, see [docker-elk-cadvisor-dashboards](https://github.com/gregbkr/docker-elk-cadvisor-dashboards) cadvisor-0.27.1/docs/storage/influxdb.md000066400000000000000000000023541315410276000201470ustar00rootroot00000000000000# Exporting cAdvisor Stats to InfluxDB cAdvisor supports exporting stats to [InfluxDB](http://influxdb.com). To use InfluxDB, you need to pass some additional flags to cAdvisor telling it where the InfluxDB instance is located: Set the storage driver as InfluxDB. ``` -storage_driver=influxdb ``` Specify what InfluxDB instance to push data to: ``` # The *ip:port* of the database. Default is 'localhost:8086' -storage_driver_host=ip:port # database name. Uses db 'cadvisor' by default -storage_driver_db # database username. Default is 'root' -storage_driver_user # database password. Default is 'root' -storage_driver_password # Use secure connection with database. False by default -storage_driver_secure # retention policy. Default is '' which corresponds to the default retention policy of the influxdb database -storage_driver_influxdb_retention_policy ``` # Examples [Brian Christner](https://www.brianchristner.io) wrote a detailed post on [setting up Docker monitoring](https://www.brianchristner.io/how-to-setup-docker-monitoring) with cAdvisor and Influxdb. A docker compose configuration for setting up cadvisor-influxdb-grafana can be found [here](https://github.com/dalekurt/docker-monitoring/blob/master/docker-compose.yml). cadvisor-0.27.1/docs/storage/kafka.md000066400000000000000000000020241315410276000174030ustar00rootroot00000000000000# Exporting cAdvisor Stats to Kafka cAdvisor supports exporting stats to [Kafka](http://kafka.apache.org/). To use Kafka, you need to provide the additional flags to cAdvisor: Set the storage driver as Kafka: ``` -storage_driver=kafka ``` If no broker are provided it will default to a broker listening at localhost:9092, with 'stats' as the default topic. Specify a Kafka broker address: ``` -storage_driver_kafka_broker_list=localhost:9092 ``` Specify a Kafka topic: ``` -storage_driver_kafka_topic=myTopic ``` As of version 9.0. Kafka supports TLS client auth: ``` # To enable TLS client auth support you need to provide the following: # Location to Certificate Authority certificate -storage_driver_kafka_ssl_ca=/path/to/ca.pem # Location to client certificate certificate -storage_driver_kafka_ssl_cert=/path/to/client_cert.pem # Location to client certificate key -storage_driver_kafka_ssl_key=/path/to/client_key.pem # Verify SSL certificate chain (default: true) -storage_driver_kafka_ssl_verify=false ``` cadvisor-0.27.1/docs/storage/prometheus.md000066400000000000000000000024361315410276000205300ustar00rootroot00000000000000# Monitoring cAdvisor with Prometheus cAdvisor exposes container statistics as [Prometheus](https://prometheus.io) metrics out of the box. By default, these metrics are served under the `/metrics` HTTP endpoint. This endpoint may be customized by setting the `-prometheus_endpoint` command-line flag. To monitor cAdvisor with Prometheus, simply configure one or more jobs in Prometheus which scrape the relevant cAdvisor processes at that metrics endpoint. For details, see Prometheus's [Configuration](https://prometheus.io/docs/operating/configuration/) documentation, as well as the [Getting started](https://prometheus.io/docs/introduction/getting_started/) guide. # Examples * [CenturyLink Labs](https://labs.ctl.io/) did an excellent write up on [Monitoring Docker services with Prometheus +cAdvisor](https://www.ctl.io/developers/blog/post/monitoring-docker-services-with-prometheus/), while it is great to get a better overview of cAdvisor integration with Prometheus, the PromDash GUI part is outdated as it has been deprecated for Grafana. * [vegasbrianc](https://github.com/vegasbrianc) provides a [starter project](https://github.com/vegasbrianc/prometheus) for cAdvisor and Prometheus monitoring, alongide a ready-to-use [Grafana dashboard](https://github.com/vegasbrianc/grafana_dashboard). cadvisor-0.27.1/docs/web.md000066400000000000000000000024461315410276000154470ustar00rootroot00000000000000# cAdvisor Web UI cAdvisor exposes a web UI at its port: `http://:/` This UI has one primary resource at `/containers` which exports live information about all containers on the machine. ## Web UI authentication You can add authentication to the web UI by either HTTP basic or HTTP digest authentication. ### HTTP basic authentication You will need to add a *http_auth_file* parameter with a HTTP basic auth file generated using htpasswd to enable HTTP basic auth. By default the auth realm is set as localhost. `./cadvisor --http_auth_file test.htpasswd --http_auth_realm localhost` The [test.htpasswd](../test.htpasswd) file provided has a username and password already added (admin:password1) for testing purposes. ### HTTP Digest authentication You will need to add a *http_digest_file* parameter with a HTTP digest auth file generated using htdigest to enable HTTP Digest auth. By default the auth realm is set as localhost. `./cadvisor --http_digest_file test.htdigest --http_digest_realm localhost` The [test.htdigest](../test.htdigest) file provided has a username and password already added (admin:password1) for testing purposes. **Note** : You can use either type of authentication, in case you decide to use both files in the arguments only HTTP basic auth will be enabled. cadvisor-0.27.1/events/000077500000000000000000000000001315410276000147165ustar00rootroot00000000000000cadvisor-0.27.1/events/handler.go000066400000000000000000000257231315410276000166730ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package events import ( "errors" "sort" "strings" "sync" "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/utils" "github.com/golang/glog" ) type byTimestamp []*info.Event // functions necessary to implement the sort interface on the Events struct func (e byTimestamp) Len() int { return len(e) } func (e byTimestamp) Swap(i, j int) { e[i], e[j] = e[j], e[i] } func (e byTimestamp) Less(i, j int) bool { return e[i].Timestamp.Before(e[j].Timestamp) } type EventChannel struct { // Watch ID. Can be used by the caller to request cancellation of watch events. watchId int // Channel on which the caller can receive watch events. channel chan *info.Event } // Request holds a set of parameters by which Event objects may be screened. // The caller may want events that occurred within a specific timeframe // or of a certain type, which may be specified in the *Request object // they pass to an EventManager function type Request struct { // events falling before StartTime do not satisfy the request. StartTime // must be left blank in calls to WatchEvents StartTime time.Time // events falling after EndTime do not satisfy the request. EndTime // must be left blank in calls to WatchEvents EndTime time.Time // EventType is a map that specifies the type(s) of events wanted EventType map[info.EventType]bool // allows the caller to put a limit on how many // events to receive. If there are more events than MaxEventsReturned // then the most chronologically recent events in the time period // specified are returned. Must be >= 1 MaxEventsReturned int // the absolute container name for which the event occurred ContainerName string // if IncludeSubcontainers is false, only events occurring in the specific // container, and not the subcontainers, will be returned IncludeSubcontainers bool } // EventManager is implemented by Events. It provides two ways to monitor // events and one way to add events type EventManager interface { // WatchEvents() allows a caller to register for receiving events based on the specified request. // On successful registration, an EventChannel object is returned. WatchEvents(request *Request) (*EventChannel, error) // GetEvents() returns all detected events based on the filters specified in request. GetEvents(request *Request) ([]*info.Event, error) // AddEvent allows the caller to add an event to an EventManager // object AddEvent(e *info.Event) error // Cancels a previously requested watch event. StopWatch(watch_id int) } // events provides an implementation for the EventManager interface. type events struct { // eventStore holds the events by event type. eventStore map[info.EventType]*utils.TimedStore // map of registered watchers keyed by watch id. watchers map[int]*watch // lock guarding the eventStore. eventsLock sync.RWMutex // lock guarding watchers. watcherLock sync.RWMutex // last allocated watch id. lastId int // Event storage policy. storagePolicy StoragePolicy } // initialized by a call to WatchEvents(), a watch struct will then be added // to the events slice of *watch objects. When AddEvent() finds an event that // satisfies the request parameter of a watch object in events.watchers, // it will send that event out over the watch object's channel. The caller that // called WatchEvents will receive the event over the channel provided to // WatchEvents type watch struct { // request parameters passed in by the caller of WatchEvents() request *Request // a channel used to send event back to the caller. eventChannel *EventChannel } func NewEventChannel(watchId int) *EventChannel { return &EventChannel{ watchId: watchId, channel: make(chan *info.Event, 10), } } // Policy specifying how many events to store. // MaxAge is the max duration for which to keep events. // MaxNumEvents is the max number of events to keep (-1 for no limit). type StoragePolicy struct { // Defaults limites, used if a per-event limit is not set. DefaultMaxAge time.Duration DefaultMaxNumEvents int // Per-event type limits. PerTypeMaxAge map[info.EventType]time.Duration PerTypeMaxNumEvents map[info.EventType]int } func DefaultStoragePolicy() StoragePolicy { return StoragePolicy{ DefaultMaxAge: 24 * time.Hour, DefaultMaxNumEvents: 100000, PerTypeMaxAge: make(map[info.EventType]time.Duration), PerTypeMaxNumEvents: make(map[info.EventType]int), } } // returns a pointer to an initialized Events object. func NewEventManager(storagePolicy StoragePolicy) *events { return &events{ eventStore: make(map[info.EventType]*utils.TimedStore, 0), watchers: make(map[int]*watch), storagePolicy: storagePolicy, } } // returns a pointer to an initialized Request object func NewRequest() *Request { return &Request{ EventType: map[info.EventType]bool{}, IncludeSubcontainers: false, MaxEventsReturned: 10, } } // returns a pointer to an initialized watch object func newWatch(request *Request, eventChannel *EventChannel) *watch { return &watch{ request: request, eventChannel: eventChannel, } } func (self *EventChannel) GetChannel() chan *info.Event { return self.channel } func (self *EventChannel) GetWatchId() int { return self.watchId } // sorts and returns up to the last MaxEventsReturned chronological elements func getMaxEventsReturned(request *Request, eSlice []*info.Event) []*info.Event { sort.Sort(byTimestamp(eSlice)) n := request.MaxEventsReturned if n >= len(eSlice) || n <= 0 { return eSlice } return eSlice[len(eSlice)-n:] } // If the request wants all subcontainers, this returns if the request's // container path is a prefix of the event container path. Otherwise, // it checks that the container paths of the event and request are // equivalent func checkIfIsSubcontainer(request *Request, event *info.Event) bool { if request.IncludeSubcontainers == true { return request.ContainerName == "/" || strings.HasPrefix(event.ContainerName+"/", request.ContainerName+"/") } return event.ContainerName == request.ContainerName } // determines if an event occurs within the time set in the request object and is the right type func checkIfEventSatisfiesRequest(request *Request, event *info.Event) bool { startTime := request.StartTime endTime := request.EndTime eventTime := event.Timestamp if !startTime.IsZero() { if startTime.After(eventTime) { return false } } if !endTime.IsZero() { if endTime.Before(eventTime) { return false } } if !request.EventType[event.EventType] { return false } if request.ContainerName != "" { return checkIfIsSubcontainer(request, event) } return true } // method of Events object that screens Event objects found in the eventStore // attribute and if they fit the parameters passed by the Request object, // adds it to a slice of *Event objects that is returned. If both MaxEventsReturned // and StartTime/EndTime are specified in the request object, then only // up to the most recent MaxEventsReturned events in that time range are returned. func (self *events) GetEvents(request *Request) ([]*info.Event, error) { returnEventList := []*info.Event{} self.eventsLock.RLock() defer self.eventsLock.RUnlock() for eventType, fetch := range request.EventType { if !fetch { continue } evs, ok := self.eventStore[eventType] if !ok { continue } res := evs.InTimeRange(request.StartTime, request.EndTime, request.MaxEventsReturned) for _, in := range res { e := in.(*info.Event) if checkIfEventSatisfiesRequest(request, e) { returnEventList = append(returnEventList, e) } } } returnEventList = getMaxEventsReturned(request, returnEventList) return returnEventList, nil } // method of Events object that maintains an *Event channel passed by the user. // When an event is added by AddEvents that satisfies the parameters in the passed // Request object it is fed to the channel. The StartTime and EndTime of the watch // request should be uninitialized because the purpose is to watch indefinitely // for events that will happen in the future func (self *events) WatchEvents(request *Request) (*EventChannel, error) { if !request.StartTime.IsZero() || !request.EndTime.IsZero() { return nil, errors.New( "for a call to watch, request.StartTime and request.EndTime must be uninitialized") } self.watcherLock.Lock() defer self.watcherLock.Unlock() new_id := self.lastId + 1 returnEventChannel := NewEventChannel(new_id) newWatcher := newWatch(request, returnEventChannel) self.watchers[new_id] = newWatcher self.lastId = new_id return returnEventChannel, nil } // helper function to update the event manager's eventStore func (self *events) updateEventStore(e *info.Event) { self.eventsLock.Lock() defer self.eventsLock.Unlock() if _, ok := self.eventStore[e.EventType]; !ok { maxNumEvents := self.storagePolicy.DefaultMaxNumEvents if numEvents, ok := self.storagePolicy.PerTypeMaxNumEvents[e.EventType]; ok { maxNumEvents = numEvents } if maxNumEvents == 0 { // Event storage is disabled for e.EventType return } maxAge := self.storagePolicy.DefaultMaxAge if age, ok := self.storagePolicy.PerTypeMaxAge[e.EventType]; ok { maxAge = age } self.eventStore[e.EventType] = utils.NewTimedStore(maxAge, maxNumEvents) } self.eventStore[e.EventType].Add(e.Timestamp, e) } func (self *events) findValidWatchers(e *info.Event) []*watch { watchesToSend := make([]*watch, 0) for _, watcher := range self.watchers { watchRequest := watcher.request if checkIfEventSatisfiesRequest(watchRequest, e) { watchesToSend = append(watchesToSend, watcher) } } return watchesToSend } // method of Events object that adds the argument Event object to the // eventStore. It also feeds the event to a set of watch channels // held by the manager if it satisfies the request keys of the channels func (self *events) AddEvent(e *info.Event) error { self.updateEventStore(e) self.watcherLock.RLock() defer self.watcherLock.RUnlock() watchesToSend := self.findValidWatchers(e) for _, watchObject := range watchesToSend { watchObject.eventChannel.GetChannel() <- e } glog.V(4).Infof("Added event %v", e) return nil } // Removes a watch instance from the EventManager's watchers map func (self *events) StopWatch(watchId int) { self.watcherLock.Lock() defer self.watcherLock.Unlock() _, ok := self.watchers[watchId] if !ok { glog.Errorf("Could not find watcher instance %v", watchId) } close(self.watchers[watchId].eventChannel.GetChannel()) delete(self.watchers, watchId) } cadvisor-0.27.1/events/handler_test.go000066400000000000000000000142151315410276000177240ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package events import ( "testing" "time" info "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/assert" ) func createOldTime(t *testing.T) time.Time { const longForm = "Jan 2, 2006 at 3:04pm (MST)" linetime, err := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)") if err != nil { t.Fatalf("could not format time.Time object") } else { return linetime } return time.Now() } // used to convert an OomInstance to an Event object func makeEvent(inTime time.Time, containerName string) *info.Event { return &info.Event{ ContainerName: containerName, Timestamp: inTime, EventType: info.EventOom, } } // returns EventManager and Request to use in tests func initializeScenario(t *testing.T) (*events, *Request, *info.Event, *info.Event) { fakeEvent := makeEvent(createOldTime(t), "/") fakeEvent2 := makeEvent(time.Now(), "/") return NewEventManager(DefaultStoragePolicy()), NewRequest(), fakeEvent, fakeEvent2 } func checkNumberOfEvents(t *testing.T, numEventsExpected int, numEventsReceived int) { if numEventsReceived != numEventsExpected { t.Fatalf("Expected to return %v events but received %v", numEventsExpected, numEventsReceived) } } func ensureProperEventReturned(t *testing.T, expectedEvent *info.Event, eventObjectFound *info.Event) { if eventObjectFound != expectedEvent { t.Errorf("Expected to find test object %v but found a different object: %v", expectedEvent, eventObjectFound) } } func TestCheckIfIsSubcontainer(t *testing.T) { myRequest := NewRequest() myRequest.ContainerName = "/root" rootRequest := NewRequest() rootRequest.ContainerName = "/" sameContainerEvent := &info.Event{ ContainerName: "/root", } subContainerEvent := &info.Event{ ContainerName: "/root/subdir", } differentContainerEvent := &info.Event{ ContainerName: "/root-completely-different-container", } if checkIfIsSubcontainer(rootRequest, sameContainerEvent) { t.Errorf("should not have found %v to be a subcontainer of %v", sameContainerEvent, rootRequest) } if !checkIfIsSubcontainer(myRequest, sameContainerEvent) { t.Errorf("should have found %v and %v had the same container name", myRequest, sameContainerEvent) } if checkIfIsSubcontainer(myRequest, subContainerEvent) { t.Errorf("should have found %v and %v had different containers", myRequest, subContainerEvent) } rootRequest.IncludeSubcontainers = true myRequest.IncludeSubcontainers = true if !checkIfIsSubcontainer(rootRequest, sameContainerEvent) { t.Errorf("should have found %v to be a subcontainer of %v", sameContainerEvent.ContainerName, rootRequest.ContainerName) } if !checkIfIsSubcontainer(myRequest, sameContainerEvent) { t.Errorf("should have found %v and %v had the same container", myRequest.ContainerName, sameContainerEvent.ContainerName) } if !checkIfIsSubcontainer(myRequest, subContainerEvent) { t.Errorf("should have found %v was a subcontainer of %v", subContainerEvent.ContainerName, myRequest.ContainerName) } if checkIfIsSubcontainer(myRequest, differentContainerEvent) { t.Errorf("should have found %v and %v had different containers", myRequest.ContainerName, differentContainerEvent.ContainerName) } } func TestWatchEventsDetectsNewEvents(t *testing.T) { myEventHolder, myRequest, fakeEvent, fakeEvent2 := initializeScenario(t) myRequest.EventType[info.EventOom] = true returnEventChannel, err := myEventHolder.WatchEvents(myRequest) assert.Nil(t, err) myEventHolder.AddEvent(fakeEvent) myEventHolder.AddEvent(fakeEvent2) startTime := time.Now() go func() { time.Sleep(5 * time.Second) if time.Since(startTime) > (5 * time.Second) { t.Errorf("Took too long to receive all the events") } }() eventsFound := 0 go func() { for event := range returnEventChannel.GetChannel() { eventsFound += 1 if eventsFound == 1 { ensureProperEventReturned(t, fakeEvent, event) } else if eventsFound == 2 { ensureProperEventReturned(t, fakeEvent2, event) break } } }() } func TestAddEventAddsEventsToEventManager(t *testing.T) { myEventHolder, _, fakeEvent, _ := initializeScenario(t) myEventHolder.AddEvent(fakeEvent) checkNumberOfEvents(t, 1, len(myEventHolder.eventStore)) ensureProperEventReturned(t, fakeEvent, myEventHolder.eventStore[info.EventOom].Get(0).(*info.Event)) } func TestGetEventsForOneEvent(t *testing.T) { myEventHolder, myRequest, fakeEvent, fakeEvent2 := initializeScenario(t) myRequest.MaxEventsReturned = 1 myRequest.EventType[info.EventOom] = true myEventHolder.AddEvent(fakeEvent) myEventHolder.AddEvent(fakeEvent2) receivedEvents, err := myEventHolder.GetEvents(myRequest) assert.Nil(t, err) checkNumberOfEvents(t, 1, len(receivedEvents)) ensureProperEventReturned(t, fakeEvent2, receivedEvents[0]) } func TestGetEventsForTimePeriod(t *testing.T) { myEventHolder, myRequest, fakeEvent, fakeEvent2 := initializeScenario(t) myRequest.StartTime = time.Now().Add(-1 * time.Second * 10) myRequest.EndTime = time.Now().Add(time.Second * 10) myRequest.EventType[info.EventOom] = true myEventHolder.AddEvent(fakeEvent) myEventHolder.AddEvent(fakeEvent2) receivedEvents, err := myEventHolder.GetEvents(myRequest) assert.Nil(t, err) checkNumberOfEvents(t, 1, len(receivedEvents)) ensureProperEventReturned(t, fakeEvent2, receivedEvents[0]) } func TestGetEventsForNoTypeRequested(t *testing.T) { myEventHolder, myRequest, fakeEvent, fakeEvent2 := initializeScenario(t) myEventHolder.AddEvent(fakeEvent) myEventHolder.AddEvent(fakeEvent2) receivedEvents, err := myEventHolder.GetEvents(myRequest) assert.Nil(t, err) checkNumberOfEvents(t, 0, len(receivedEvents)) } cadvisor-0.27.1/fs/000077500000000000000000000000001315410276000140225ustar00rootroot00000000000000cadvisor-0.27.1/fs/fs.go000066400000000000000000000536451315410276000147760ustar00rootroot00000000000000// 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. // +build linux // Provides Filesystem Stats package fs import ( "bufio" "bytes" "fmt" "io/ioutil" "os" "os/exec" "path" "path/filepath" "regexp" "strconv" "strings" "syscall" "time" "github.com/docker/docker/pkg/mount" "github.com/golang/glog" "github.com/google/cadvisor/devicemapper" dockerutil "github.com/google/cadvisor/utils/docker" zfs "github.com/mistifyio/go-zfs" ) const ( LabelSystemRoot = "root" LabelDockerImages = "docker-images" LabelRktImages = "rkt-images" LabelCrioImages = "crio-images" ) // The maximum number of `du` and `find` tasks that can be running at once. const maxConcurrentOps = 20 // A pool for restricting the number of consecutive `du` and `find` tasks running. var pool = make(chan struct{}, maxConcurrentOps) func init() { for i := 0; i < maxConcurrentOps; i++ { releaseToken() } } func claimToken() { <-pool } func releaseToken() { pool <- struct{}{} } type partition struct { mountpoint string major uint minor uint fsType string blockSize uint } type RealFsInfo struct { // Map from block device path to partition information. partitions map[string]partition // Map from label to block device path. // Labels are intent-specific tags that are auto-detected. labels map[string]string // Map from mountpoint to mount information. mounts map[string]*mount.Info // devicemapper client dmsetup devicemapper.DmsetupClient // fsUUIDToDeviceName is a map from the filesystem UUID to its device name. fsUUIDToDeviceName map[string]string } type Context struct { // docker root directory. Docker DockerContext RktPath string Crio CrioContext } type DockerContext struct { Root string Driver string DriverStatus map[string]string } type CrioContext struct { Root string } func NewFsInfo(context Context) (FsInfo, error) { mounts, err := mount.GetMounts() if err != nil { return nil, err } fsUUIDToDeviceName, err := getFsUUIDToDeviceNameMap() if err != nil { return nil, err } // Avoid devicemapper container mounts - these are tracked by the ThinPoolWatcher excluded := []string{fmt.Sprintf("%s/devicemapper/mnt", context.Docker.Root)} fsInfo := &RealFsInfo{ partitions: processMounts(mounts, excluded), labels: make(map[string]string, 0), mounts: make(map[string]*mount.Info, 0), dmsetup: devicemapper.NewDmsetupClient(), fsUUIDToDeviceName: fsUUIDToDeviceName, } for _, mount := range mounts { fsInfo.mounts[mount.Mountpoint] = mount } fsInfo.addRktImagesLabel(context, mounts) // need to call this before the log line below printing out the partitions, as this function may // add a "partition" for devicemapper to fsInfo.partitions fsInfo.addDockerImagesLabel(context, mounts) fsInfo.addCrioImagesLabel(context, mounts) glog.Infof("Filesystem UUIDs: %+v", fsInfo.fsUUIDToDeviceName) glog.Infof("Filesystem partitions: %+v", fsInfo.partitions) fsInfo.addSystemRootLabel(mounts) return fsInfo, nil } // getFsUUIDToDeviceNameMap creates the filesystem uuid to device name map // using the information in /dev/disk/by-uuid. If the directory does not exist, // this function will return an empty map. func getFsUUIDToDeviceNameMap() (map[string]string, error) { const dir = "/dev/disk/by-uuid" if _, err := os.Stat(dir); os.IsNotExist(err) { return make(map[string]string), nil } files, err := ioutil.ReadDir(dir) if err != nil { return nil, err } fsUUIDToDeviceName := make(map[string]string) for _, file := range files { path := filepath.Join(dir, file.Name()) target, err := os.Readlink(path) if err != nil { glog.Infof("Failed to resolve symlink for %q", path) continue } device, err := filepath.Abs(filepath.Join(dir, target)) if err != nil { return nil, fmt.Errorf("failed to resolve the absolute path of %q", filepath.Join(dir, target)) } fsUUIDToDeviceName[file.Name()] = device } return fsUUIDToDeviceName, nil } func processMounts(mounts []*mount.Info, excludedMountpointPrefixes []string) map[string]partition { partitions := make(map[string]partition, 0) supportedFsType := map[string]bool{ // all ext systems are checked through prefix. "btrfs": true, "tmpfs": true, "xfs": true, "zfs": true, } for _, mount := range mounts { if !strings.HasPrefix(mount.Fstype, "ext") && !supportedFsType[mount.Fstype] { continue } // Avoid bind mounts. if _, ok := partitions[mount.Source]; ok { continue } hasPrefix := false for _, prefix := range excludedMountpointPrefixes { if strings.HasPrefix(mount.Mountpoint, prefix) { hasPrefix = true break } } if hasPrefix { continue } // btrfs fix: following workaround fixes wrong btrfs Major and Minor Ids reported in /proc/self/mountinfo. // instead of using values from /proc/self/mountinfo we use stat to get Ids from btrfs mount point if mount.Fstype == "btrfs" && mount.Major == 0 && strings.HasPrefix(mount.Source, "/dev/") { major, minor, err := getBtrfsMajorMinorIds(mount) if err != nil { glog.Warningf("%s", err) } else { mount.Major = major mount.Minor = minor } } partitions[mount.Source] = partition{ fsType: mount.Fstype, mountpoint: mount.Mountpoint, major: uint(mount.Major), minor: uint(mount.Minor), } } return partitions } // getDockerDeviceMapperInfo returns information about the devicemapper device and "partition" if // docker is using devicemapper for its storage driver. If a loopback device is being used, don't // return any information or error, as we want to report based on the actual partition where the // loopback file resides, inside of the loopback file itself. func (self *RealFsInfo) getDockerDeviceMapperInfo(context DockerContext) (string, *partition, error) { if context.Driver != DeviceMapper.String() { return "", nil, nil } dataLoopFile := context.DriverStatus[dockerutil.DriverStatusDataLoopFile] if len(dataLoopFile) > 0 { return "", nil, nil } dev, major, minor, blockSize, err := dockerDMDevice(context.DriverStatus, self.dmsetup) if err != nil { return "", nil, err } return dev, &partition{ fsType: DeviceMapper.String(), major: major, minor: minor, blockSize: blockSize, }, nil } // addSystemRootLabel attempts to determine which device contains the mount for /. func (self *RealFsInfo) addSystemRootLabel(mounts []*mount.Info) { for _, m := range mounts { if m.Mountpoint == "/" { self.partitions[m.Source] = partition{ fsType: m.Fstype, mountpoint: m.Mountpoint, major: uint(m.Major), minor: uint(m.Minor), } self.labels[LabelSystemRoot] = m.Source return } } } // addDockerImagesLabel attempts to determine which device contains the mount for docker images. func (self *RealFsInfo) addDockerImagesLabel(context Context, mounts []*mount.Info) { dockerDev, dockerPartition, err := self.getDockerDeviceMapperInfo(context.Docker) if err != nil { glog.Warningf("Could not get Docker devicemapper device: %v", err) } if len(dockerDev) > 0 && dockerPartition != nil { self.partitions[dockerDev] = *dockerPartition self.labels[LabelDockerImages] = dockerDev } else { self.updateContainerImagesPath(LabelDockerImages, mounts, getDockerImagePaths(context)) } } func (self *RealFsInfo) addCrioImagesLabel(context Context, mounts []*mount.Info) { if context.Crio.Root != "" { crioPath := context.Crio.Root crioImagePaths := map[string]struct{}{ "/": {}, } for _, dir := range []string{"overlay", "overlay2"} { crioImagePaths[path.Join(crioPath, dir+"-images")] = struct{}{} } for crioPath != "/" && crioPath != "." { crioImagePaths[crioPath] = struct{}{} crioPath = filepath.Dir(crioPath) } self.updateContainerImagesPath(LabelCrioImages, mounts, crioImagePaths) } } func (self *RealFsInfo) addRktImagesLabel(context Context, mounts []*mount.Info) { if context.RktPath != "" { rktPath := context.RktPath rktImagesPaths := map[string]struct{}{ "/": {}, } for rktPath != "/" && rktPath != "." { rktImagesPaths[rktPath] = struct{}{} rktPath = filepath.Dir(rktPath) } self.updateContainerImagesPath(LabelRktImages, mounts, rktImagesPaths) } } // Generate a list of possible mount points for docker image management from the docker root directory. // Right now, we look for each type of supported graph driver directories, but we can do better by parsing // some of the context from `docker info`. func getDockerImagePaths(context Context) map[string]struct{} { dockerImagePaths := map[string]struct{}{ "/": {}, } // TODO(rjnagal): Detect docker root and graphdriver directories from docker info. dockerRoot := context.Docker.Root for _, dir := range []string{"devicemapper", "btrfs", "aufs", "overlay", "overlay2", "zfs"} { dockerImagePaths[path.Join(dockerRoot, dir)] = struct{}{} } for dockerRoot != "/" && dockerRoot != "." { dockerImagePaths[dockerRoot] = struct{}{} dockerRoot = filepath.Dir(dockerRoot) } return dockerImagePaths } // This method compares the mountpoints with possible container image mount points. If a match is found, // the label is added to the partition. func (self *RealFsInfo) updateContainerImagesPath(label string, mounts []*mount.Info, containerImagePaths map[string]struct{}) { var useMount *mount.Info for _, m := range mounts { if _, ok := containerImagePaths[m.Mountpoint]; ok { if useMount == nil || (len(useMount.Mountpoint) < len(m.Mountpoint)) { useMount = m } } } if useMount != nil { self.partitions[useMount.Source] = partition{ fsType: useMount.Fstype, mountpoint: useMount.Mountpoint, major: uint(useMount.Major), minor: uint(useMount.Minor), } self.labels[label] = useMount.Source } } func (self *RealFsInfo) GetDeviceForLabel(label string) (string, error) { dev, ok := self.labels[label] if !ok { return "", fmt.Errorf("non-existent label %q", label) } return dev, nil } func (self *RealFsInfo) GetLabelsForDevice(device string) ([]string, error) { labels := []string{} for label, dev := range self.labels { if dev == device { labels = append(labels, label) } } return labels, nil } func (self *RealFsInfo) GetMountpointForDevice(dev string) (string, error) { p, ok := self.partitions[dev] if !ok { return "", fmt.Errorf("no partition info for device %q", dev) } return p.mountpoint, nil } func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error) { filesystems := make([]Fs, 0) deviceSet := make(map[string]struct{}) diskStatsMap, err := getDiskStatsMap("/proc/diskstats") if err != nil { return nil, err } for device, partition := range self.partitions { _, hasMount := mountSet[partition.mountpoint] _, hasDevice := deviceSet[device] if mountSet == nil || (hasMount && !hasDevice) { var ( err error fs Fs ) switch partition.fsType { case DeviceMapper.String(): fs.Capacity, fs.Free, fs.Available, err = getDMStats(device, partition.blockSize) glog.V(5).Infof("got devicemapper fs capacity stats: capacity: %v free: %v available: %v:", fs.Capacity, fs.Free, fs.Available) fs.Type = DeviceMapper case ZFS.String(): fs.Capacity, fs.Free, fs.Available, err = getZfstats(device) fs.Type = ZFS default: var inodes, inodesFree uint64 fs.Capacity, fs.Free, fs.Available, inodes, inodesFree, err = getVfsStats(partition.mountpoint) fs.Inodes = &inodes fs.InodesFree = &inodesFree fs.Type = VFS } if err != nil { glog.Errorf("Stat fs failed. Error: %v", err) } else { deviceSet[device] = struct{}{} fs.DeviceInfo = DeviceInfo{ Device: device, Major: uint(partition.major), Minor: uint(partition.minor), } fs.DiskStats = diskStatsMap[device] filesystems = append(filesystems, fs) } } } return filesystems, nil } var partitionRegex = regexp.MustCompile(`^(?:(?:s|v|xv)d[a-z]+\d*|dm-\d+)$`) func getDiskStatsMap(diskStatsFile string) (map[string]DiskStats, error) { diskStatsMap := make(map[string]DiskStats) file, err := os.Open(diskStatsFile) if err != nil { if os.IsNotExist(err) { glog.Infof("not collecting filesystem statistics because file %q was not available", diskStatsFile) return diskStatsMap, nil } return nil, err } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() words := strings.Fields(line) if !partitionRegex.MatchString(words[2]) { continue } // 8 50 sdd2 40 0 280 223 7 0 22 108 0 330 330 deviceName := path.Join("/dev", words[2]) wordLength := len(words) offset := 3 var stats = make([]uint64, wordLength-offset) if len(stats) < 11 { return nil, fmt.Errorf("could not parse all 11 columns of /proc/diskstats") } var error error for i := offset; i < wordLength; i++ { stats[i-offset], error = strconv.ParseUint(words[i], 10, 64) if error != nil { return nil, error } } diskStats := DiskStats{ ReadsCompleted: stats[0], ReadsMerged: stats[1], SectorsRead: stats[2], ReadTime: stats[3], WritesCompleted: stats[4], WritesMerged: stats[5], SectorsWritten: stats[6], WriteTime: stats[7], IoInProgress: stats[8], IoTime: stats[9], WeightedIoTime: stats[10], } diskStatsMap[deviceName] = diskStats } return diskStatsMap, nil } func (self *RealFsInfo) GetGlobalFsInfo() ([]Fs, error) { return self.GetFsInfoForPath(nil) } func major(devNumber uint64) uint { return uint((devNumber >> 8) & 0xfff) } func minor(devNumber uint64) uint { return uint((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00)) } func (self *RealFsInfo) GetDeviceInfoByFsUUID(uuid string) (*DeviceInfo, error) { deviceName, found := self.fsUUIDToDeviceName[uuid] if !found { return nil, ErrNoSuchDevice } p, found := self.partitions[deviceName] if !found { return nil, fmt.Errorf("cannot find device %q in partitions", deviceName) } return &DeviceInfo{deviceName, p.major, p.minor}, nil } func (self *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) { buf := new(syscall.Stat_t) err := syscall.Stat(dir, buf) if err != nil { return nil, fmt.Errorf("stat failed on %s with error: %s", dir, err) } major := major(buf.Dev) minor := minor(buf.Dev) for device, partition := range self.partitions { if partition.major == major && partition.minor == minor { return &DeviceInfo{device, major, minor}, nil } } mount, found := self.mounts[dir] if found && mount.Fstype == "btrfs" && mount.Major == 0 && strings.HasPrefix(mount.Source, "/dev/") { major, minor, err := getBtrfsMajorMinorIds(mount) if err != nil { glog.Warningf("%s", err) } else { return &DeviceInfo{mount.Source, uint(major), uint(minor)}, nil } } return nil, fmt.Errorf("could not find device with major: %d, minor: %d in cached partitions map", major, minor) } func (self *RealFsInfo) GetDirDiskUsage(dir string, timeout time.Duration) (uint64, error) { claimToken() defer releaseToken() return GetDirDiskUsage(dir, timeout) } func GetDirDiskUsage(dir string, timeout time.Duration) (uint64, error) { if dir == "" { return 0, fmt.Errorf("invalid directory") } cmd := exec.Command("nice", "-n", "19", "du", "-s", dir) stdoutp, err := cmd.StdoutPipe() if err != nil { return 0, fmt.Errorf("failed to setup stdout for cmd %v - %v", cmd.Args, err) } stderrp, err := cmd.StderrPipe() if err != nil { return 0, fmt.Errorf("failed to setup stderr for cmd %v - %v", cmd.Args, err) } if err := cmd.Start(); err != nil { return 0, fmt.Errorf("failed to exec du - %v", err) } timer := time.AfterFunc(timeout, func() { glog.Infof("killing cmd %v due to timeout(%s)", cmd.Args, timeout.String()) cmd.Process.Kill() }) stdoutb, souterr := ioutil.ReadAll(stdoutp) if souterr != nil { glog.Errorf("failed to read from stdout for cmd %v - %v", cmd.Args, souterr) } stderrb, _ := ioutil.ReadAll(stderrp) err = cmd.Wait() timer.Stop() if err != nil { return 0, fmt.Errorf("du command failed on %s with output stdout: %s, stderr: %s - %v", dir, string(stdoutb), string(stderrb), err) } stdout := string(stdoutb) usageInKb, err := strconv.ParseUint(strings.Fields(stdout)[0], 10, 64) if err != nil { return 0, fmt.Errorf("cannot parse 'du' output %s - %s", stdout, err) } return usageInKb * 1024, nil } func (self *RealFsInfo) GetDirInodeUsage(dir string, timeout time.Duration) (uint64, error) { claimToken() defer releaseToken() return GetDirInodeUsage(dir, timeout) } func GetDirInodeUsage(dir string, timeout time.Duration) (uint64, error) { if dir == "" { return 0, fmt.Errorf("invalid directory") } var counter byteCounter var stderr bytes.Buffer findCmd := exec.Command("find", dir, "-xdev", "-printf", ".") findCmd.Stdout, findCmd.Stderr = &counter, &stderr if err := findCmd.Start(); err != nil { return 0, fmt.Errorf("failed to exec cmd %v - %v; stderr: %v", findCmd.Args, err, stderr.String()) } timer := time.AfterFunc(timeout, func() { glog.Infof("killing cmd %v due to timeout(%s)", findCmd.Args, timeout.String()) findCmd.Process.Kill() }) if err := findCmd.Wait(); err != nil { return 0, fmt.Errorf("cmd %v failed. stderr: %s; err: %v", findCmd.Args, stderr.String(), err) } timer.Stop() return counter.bytesWritten, nil } func getVfsStats(path string) (total uint64, free uint64, avail uint64, inodes uint64, inodesFree uint64, err error) { var s syscall.Statfs_t if err = syscall.Statfs(path, &s); err != nil { return 0, 0, 0, 0, 0, err } total = uint64(s.Frsize) * s.Blocks free = uint64(s.Frsize) * s.Bfree avail = uint64(s.Frsize) * s.Bavail inodes = uint64(s.Files) inodesFree = uint64(s.Ffree) return total, free, avail, inodes, inodesFree, nil } // Devicemapper thin provisioning is detailed at // https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt func dockerDMDevice(driverStatus map[string]string, dmsetup devicemapper.DmsetupClient) (string, uint, uint, uint, error) { poolName, ok := driverStatus[dockerutil.DriverStatusPoolName] if !ok || len(poolName) == 0 { return "", 0, 0, 0, fmt.Errorf("Could not get dm pool name") } out, err := dmsetup.Table(poolName) if err != nil { return "", 0, 0, 0, err } major, minor, dataBlkSize, err := parseDMTable(string(out)) if err != nil { return "", 0, 0, 0, err } return poolName, major, minor, dataBlkSize, nil } // parseDMTable parses a single line of `dmsetup table` output and returns the // major device, minor device, block size, and an error. func parseDMTable(dmTable string) (uint, uint, uint, error) { dmTable = strings.Replace(dmTable, ":", " ", -1) dmFields := strings.Fields(dmTable) if len(dmFields) < 8 { return 0, 0, 0, fmt.Errorf("Invalid dmsetup status output: %s", dmTable) } major, err := strconv.ParseUint(dmFields[5], 10, 32) if err != nil { return 0, 0, 0, err } minor, err := strconv.ParseUint(dmFields[6], 10, 32) if err != nil { return 0, 0, 0, err } dataBlkSize, err := strconv.ParseUint(dmFields[7], 10, 32) if err != nil { return 0, 0, 0, err } return uint(major), uint(minor), uint(dataBlkSize), nil } func getDMStats(poolName string, dataBlkSize uint) (uint64, uint64, uint64, error) { out, err := exec.Command("dmsetup", "status", poolName).Output() if err != nil { return 0, 0, 0, err } used, total, err := parseDMStatus(string(out)) if err != nil { return 0, 0, 0, err } used *= 512 * uint64(dataBlkSize) total *= 512 * uint64(dataBlkSize) free := total - used return total, free, free, nil } func parseDMStatus(dmStatus string) (uint64, uint64, error) { dmStatus = strings.Replace(dmStatus, "/", " ", -1) dmFields := strings.Fields(dmStatus) if len(dmFields) < 8 { return 0, 0, fmt.Errorf("Invalid dmsetup status output: %s", dmStatus) } used, err := strconv.ParseUint(dmFields[6], 10, 64) if err != nil { return 0, 0, err } total, err := strconv.ParseUint(dmFields[7], 10, 64) if err != nil { return 0, 0, err } return used, total, nil } // getZfstats returns ZFS mount stats using zfsutils func getZfstats(poolName string) (uint64, uint64, uint64, error) { dataset, err := zfs.GetDataset(poolName) if err != nil { return 0, 0, 0, err } total := dataset.Used + dataset.Avail + dataset.Usedbydataset return total, dataset.Avail, dataset.Avail, nil } // Simple io.Writer implementation that counts how many bytes were written. type byteCounter struct{ bytesWritten uint64 } func (b *byteCounter) Write(p []byte) (int, error) { b.bytesWritten += uint64(len(p)) return len(p), nil } // Get major and minor Ids for a mount point using btrfs as filesystem. func getBtrfsMajorMinorIds(mount *mount.Info) (int, int, error) { // btrfs fix: following workaround fixes wrong btrfs Major and Minor Ids reported in /proc/self/mountinfo. // instead of using values from /proc/self/mountinfo we use stat to get Ids from btrfs mount point buf := new(syscall.Stat_t) err := syscall.Stat(mount.Source, buf) if err != nil { err = fmt.Errorf("stat failed on %s with error: %s", mount.Source, err) return 0, 0, err } glog.Infof("btrfs mount %#v", mount) if buf.Mode&syscall.S_IFMT == syscall.S_IFBLK { err := syscall.Stat(mount.Mountpoint, buf) if err != nil { err = fmt.Errorf("stat failed on %s with error: %s", mount.Mountpoint, err) return 0, 0, err } glog.Infof("btrfs dev major:minor %d:%d\n", int(major(buf.Dev)), int(minor(buf.Dev))) glog.Infof("btrfs rdev major:minor %d:%d\n", int(major(buf.Rdev)), int(minor(buf.Rdev))) return int(major(buf.Dev)), int(minor(buf.Dev)), nil } else { return 0, 0, fmt.Errorf("%s is not a block device", mount.Source) } } cadvisor-0.27.1/fs/fs_test.go000066400000000000000000000360041315410276000160230ustar00rootroot00000000000000// 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 fs import ( "errors" "io/ioutil" "os" "reflect" "testing" "time" "github.com/docker/docker/pkg/mount" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGetDiskStatsMap(t *testing.T) { diskStatsMap, err := getDiskStatsMap("test_resources/diskstats") if err != nil { t.Errorf("Error calling getDiskStatMap %s", err) } if len(diskStatsMap) != 30 { t.Errorf("diskStatsMap %+v not valid", diskStatsMap) } keySet := map[string]string{ "/dev/sda": "/dev/sda", "/dev/sdb": "/dev/sdb", "/dev/sdc": "/dev/sdc", "/dev/sdd": "/dev/sdd", "/dev/sde": "/dev/sde", "/dev/sdf": "/dev/sdf", "/dev/sdg": "/dev/sdg", "/dev/sdh": "/dev/sdh", "/dev/sdb1": "/dev/sdb1", "/dev/sdb2": "/dev/sdb2", "/dev/sda1": "/dev/sda1", "/dev/sda2": "/dev/sda2", "/dev/sdc1": "/dev/sdc1", "/dev/sdc2": "/dev/sdc2", "/dev/sdc3": "/dev/sdc3", "/dev/sdc4": "/dev/sdc4", "/dev/sdd1": "/dev/sdd1", "/dev/sdd2": "/dev/sdd2", "/dev/sdd3": "/dev/sdd3", "/dev/sdd4": "/dev/sdd4", "/dev/sde1": "/dev/sde1", "/dev/sde2": "/dev/sde2", "/dev/sdf1": "/dev/sdf1", "/dev/sdf2": "/dev/sdf2", "/dev/sdg1": "/dev/sdg1", "/dev/sdg2": "/dev/sdg2", "/dev/sdh1": "/dev/sdh1", "/dev/sdh2": "/dev/sdh2", "/dev/dm-0": "/dev/dm-0", "/dev/dm-1": "/dev/dm-1", } for device := range diskStatsMap { if _, ok := keySet[device]; !ok { t.Errorf("Cannot find device %s", device) } delete(keySet, device) } if len(keySet) != 0 { t.Errorf("diskStatsMap %+v contains illegal keys %+v", diskStatsMap, keySet) } } func TestFileNotExist(t *testing.T) { _, err := getDiskStatsMap("/file_does_not_exist") if err != nil { t.Fatalf("getDiskStatsMap must not error for absent file: %s", err) } } func TestDirDiskUsage(t *testing.T) { as := assert.New(t) fsInfo, err := NewFsInfo(Context{}) as.NoError(err) dir, err := ioutil.TempDir(os.TempDir(), "") as.NoError(err) defer os.RemoveAll(dir) dataSize := 1024 * 100 //100 KB b := make([]byte, dataSize) f, err := ioutil.TempFile(dir, "") as.NoError(err) as.NoError(ioutil.WriteFile(f.Name(), b, 0700)) fi, err := f.Stat() as.NoError(err) expectedSize := uint64(fi.Size()) size, err := fsInfo.GetDirDiskUsage(dir, time.Minute) as.NoError(err) as.True(expectedSize <= size, "expected dir size to be at-least %d; got size: %d", expectedSize, size) } func TestDirInodeUsage(t *testing.T) { as := assert.New(t) fsInfo, err := NewFsInfo(Context{}) as.NoError(err) dir, err := ioutil.TempDir(os.TempDir(), "") as.NoError(err) defer os.RemoveAll(dir) numFiles := 1000 for i := 0; i < numFiles; i++ { _, err := ioutil.TempFile(dir, "") require.NoError(t, err) } inodes, err := fsInfo.GetDirInodeUsage(dir, time.Minute) as.NoError(err) // We sould get numFiles+1 inodes, since we get 1 inode for each file, plus 1 for the directory as.True(uint64(numFiles+1) == inodes, "expected inodes in dir to be %d; got inodes: %d", numFiles+1, inodes) } var dmStatusTests = []struct { dmStatus string used uint64 total uint64 errExpected bool }{ {`0 409534464 thin-pool 64085 3705/4161600 88106/3199488 - rw no_discard_passdown queue_if_no_space -`, 88106, 3199488, false}, {`0 209715200 thin-pool 707 1215/524288 30282/1638400 - rw discard_passdown`, 30282, 1638400, false}, {`Invalid status line`, 0, 0, false}, } func TestParseDMStatus(t *testing.T) { for _, tt := range dmStatusTests { used, total, err := parseDMStatus(tt.dmStatus) if tt.errExpected && err != nil { t.Errorf("parseDMStatus(%q) expected error", tt.dmStatus) } if used != tt.used { t.Errorf("parseDMStatus(%q) wrong used value => %q, want %q", tt.dmStatus, used, tt.used) } if total != tt.total { t.Errorf("parseDMStatus(%q) wrong total value => %q, want %q", tt.dmStatus, total, tt.total) } } } var dmTableTests = []struct { dmTable string major uint minor uint dataBlkSize uint errExpected bool }{ {`0 409534464 thin-pool 253:6 253:7 128 32768 1 skip_block_zeroing`, 253, 7, 128, false}, {`0 409534464 thin-pool 253:6 258:9 512 32768 1 skip_block_zeroing otherstuff`, 258, 9, 512, false}, {`Invalid status line`, 0, 0, 0, false}, } func TestParseDMTable(t *testing.T) { for _, tt := range dmTableTests { major, minor, dataBlkSize, err := parseDMTable(tt.dmTable) if tt.errExpected && err != nil { t.Errorf("parseDMTable(%q) expected error", tt.dmTable) } if major != tt.major { t.Errorf("parseDMTable(%q) wrong major value => %q, want %q", tt.dmTable, major, tt.major) } if minor != tt.minor { t.Errorf("parseDMTable(%q) wrong minor value => %q, want %q", tt.dmTable, minor, tt.minor) } if dataBlkSize != tt.dataBlkSize { t.Errorf("parseDMTable(%q) wrong dataBlkSize value => %q, want %q", tt.dmTable, dataBlkSize, tt.dataBlkSize) } } } func TestAddSystemRootLabel(t *testing.T) { tests := []struct { mounts []*mount.Info expected string }{ { mounts: []*mount.Info{ {Source: "/dev/sda1", Mountpoint: "/foo"}, {Source: "/dev/sdb1", Mountpoint: "/"}, }, expected: "/dev/sdb1", }, } for i, tt := range tests { fsInfo := &RealFsInfo{ labels: map[string]string{}, partitions: map[string]partition{}, } fsInfo.addSystemRootLabel(tt.mounts) if source, ok := fsInfo.labels[LabelSystemRoot]; !ok || source != tt.expected { t.Errorf("case %d: expected mount source '%s', got '%s'", i, tt.expected, source) } } } type testDmsetup struct { data []byte err error } func (*testDmsetup) Message(deviceName string, sector int, message string) ([]byte, error) { return nil, nil } func (*testDmsetup) Status(deviceName string) ([]byte, error) { return nil, nil } func (t *testDmsetup) Table(poolName string) ([]byte, error) { return t.data, t.err } func TestGetDockerDeviceMapperInfo(t *testing.T) { tests := []struct { name string driver string driverStatus map[string]string dmsetupTable string dmsetupTableError error expectedDevice string expectedPartition *partition expectedError bool }{ { name: "not devicemapper", driver: "btrfs", expectedDevice: "", expectedPartition: nil, expectedError: false, }, { name: "nil driver status", driver: "devicemapper", driverStatus: nil, expectedDevice: "", expectedPartition: nil, expectedError: true, }, { name: "loopback", driver: "devicemapper", driverStatus: map[string]string{"Data loop file": "/var/lib/docker/devicemapper/devicemapper/data"}, expectedDevice: "", expectedPartition: nil, expectedError: false, }, { name: "missing pool name", driver: "devicemapper", driverStatus: map[string]string{}, expectedDevice: "", expectedPartition: nil, expectedError: true, }, { name: "error invoking dmsetup", driver: "devicemapper", driverStatus: map[string]string{"Pool Name": "vg_vagrant-docker--pool"}, dmsetupTableError: errors.New("foo"), expectedDevice: "", expectedPartition: nil, expectedError: true, }, { name: "unable to parse dmsetup table", driver: "devicemapper", driverStatus: map[string]string{"Pool Name": "vg_vagrant-docker--pool"}, dmsetupTable: "no data here!", expectedDevice: "", expectedPartition: nil, expectedError: true, }, { name: "happy path", driver: "devicemapper", driverStatus: map[string]string{"Pool Name": "vg_vagrant-docker--pool"}, dmsetupTable: "0 53870592 thin-pool 253:2 253:3 1024 0 1 skip_block_zeroing", expectedDevice: "vg_vagrant-docker--pool", expectedPartition: &partition{ fsType: "devicemapper", major: 253, minor: 3, blockSize: 1024, }, expectedError: false, }, } for _, tt := range tests { fsInfo := &RealFsInfo{ dmsetup: &testDmsetup{ data: []byte(tt.dmsetupTable), }, } dockerCtx := DockerContext{ Driver: tt.driver, DriverStatus: tt.driverStatus, } device, partition, err := fsInfo.getDockerDeviceMapperInfo(dockerCtx) if tt.expectedError && err == nil { t.Errorf("%s: expected error but got nil", tt.name) continue } if !tt.expectedError && err != nil { t.Errorf("%s: unexpected error: %v", tt.name, err) continue } if e, a := tt.expectedDevice, device; e != a { t.Errorf("%s: device: expected %q, got %q", tt.name, e, a) } if e, a := tt.expectedPartition, partition; !reflect.DeepEqual(e, a) { t.Errorf("%s: partition: expected %#v, got %#v", tt.name, e, a) } } } func TestAddDockerImagesLabel(t *testing.T) { tests := []struct { name string driver string driverStatus map[string]string dmsetupTable string getDockerDeviceMapperInfoError error mounts []*mount.Info expectedDockerDevice string expectedPartition *partition }{ { name: "devicemapper, not loopback", driver: "devicemapper", driverStatus: map[string]string{"Pool Name": "vg_vagrant-docker--pool"}, dmsetupTable: "0 53870592 thin-pool 253:2 253:3 1024 0 1 skip_block_zeroing", mounts: []*mount.Info{ { Source: "/dev/mapper/vg_vagrant-lv_root", Mountpoint: "/", Fstype: "devicemapper", }, }, expectedDockerDevice: "vg_vagrant-docker--pool", expectedPartition: &partition{ fsType: "devicemapper", major: 253, minor: 3, blockSize: 1024, }, }, { name: "devicemapper, loopback on non-root partition", driver: "devicemapper", driverStatus: map[string]string{"Data loop file": "/var/lib/docker/devicemapper/devicemapper/data"}, mounts: []*mount.Info{ { Source: "/dev/mapper/vg_vagrant-lv_root", Mountpoint: "/", Fstype: "devicemapper", }, { Source: "/dev/sdb1", Mountpoint: "/var/lib/docker/devicemapper", }, }, expectedDockerDevice: "/dev/sdb1", }, { name: "multiple mounts - innermost check", mounts: []*mount.Info{ { Source: "/dev/sda1", Mountpoint: "/", Fstype: "ext4", }, { Source: "/dev/sdb1", Mountpoint: "/var/lib/docker", Fstype: "ext4", }, { Source: "/dev/sdb2", Mountpoint: "/var/lib/docker/btrfs", Fstype: "btrfs", }, }, expectedDockerDevice: "/dev/sdb2", }, { name: "root fs inside container, docker-images bindmount", mounts: []*mount.Info{ { Source: "overlay", Mountpoint: "/", Fstype: "overlay", }, { Source: "/dev/sda1", Mountpoint: "/var/lib/docker", Fstype: "ext4", }, }, expectedDockerDevice: "/dev/sda1", }, { name: "[overlay2] root fs inside container - /var/lib/docker bindmount", mounts: []*mount.Info{ { Source: "overlay", Mountpoint: "/", Fstype: "overlay", }, { Source: "/dev/sdb1", Mountpoint: "/var/lib/docker", Fstype: "ext4", }, { Source: "/dev/sdb2", Mountpoint: "/var/lib/docker/overlay2", Fstype: "ext4", }, }, expectedDockerDevice: "/dev/sdb2", }, } for _, tt := range tests { fsInfo := &RealFsInfo{ labels: map[string]string{}, partitions: map[string]partition{}, dmsetup: &testDmsetup{ data: []byte(tt.dmsetupTable), }, } context := Context{ Docker: DockerContext{ Root: "/var/lib/docker", Driver: tt.driver, DriverStatus: tt.driverStatus, }, } fsInfo.addDockerImagesLabel(context, tt.mounts) if e, a := tt.expectedDockerDevice, fsInfo.labels[LabelDockerImages]; e != a { t.Errorf("%s: docker device: expected %q, got %q", tt.name, e, a) } if tt.expectedPartition == nil { continue } if e, a := *tt.expectedPartition, fsInfo.partitions[tt.expectedDockerDevice]; !reflect.DeepEqual(e, a) { t.Errorf("%s: docker partition: expected %#v, got %#v", tt.name, e, a) } } } func TestProcessMounts(t *testing.T) { tests := []struct { name string mounts []*mount.Info excludedPrefixes []string expected map[string]partition }{ { name: "unsupported fs types", mounts: []*mount.Info{ {Fstype: "overlay"}, {Fstype: "somethingelse"}, }, expected: map[string]partition{}, }, { name: "avoid bind mounts", mounts: []*mount.Info{ {Root: "/", Mountpoint: "/", Source: "/dev/sda1", Fstype: "xfs", Major: 253, Minor: 0}, {Root: "/foo", Mountpoint: "/bar", Source: "/dev/sda1", Fstype: "xfs", Major: 253, Minor: 0}, }, expected: map[string]partition{ "/dev/sda1": {fsType: "xfs", mountpoint: "/", major: 253, minor: 0}, }, }, { name: "exclude prefixes", mounts: []*mount.Info{ {Root: "/", Mountpoint: "/someother", Source: "/dev/sda1", Fstype: "xfs", Major: 253, Minor: 2}, {Root: "/", Mountpoint: "/", Source: "/dev/sda2", Fstype: "xfs", Major: 253, Minor: 0}, {Root: "/", Mountpoint: "/excludeme", Source: "/dev/sda3", Fstype: "xfs", Major: 253, Minor: 1}, }, excludedPrefixes: []string{"/exclude", "/some"}, expected: map[string]partition{ "/dev/sda2": {fsType: "xfs", mountpoint: "/", major: 253, minor: 0}, }, }, { name: "supported fs types", mounts: []*mount.Info{ {Root: "/", Mountpoint: "/a", Source: "/dev/sda", Fstype: "ext3", Major: 253, Minor: 0}, {Root: "/", Mountpoint: "/b", Source: "/dev/sdb", Fstype: "ext4", Major: 253, Minor: 1}, {Root: "/", Mountpoint: "/c", Source: "/dev/sdc", Fstype: "btrfs", Major: 253, Minor: 2}, {Root: "/", Mountpoint: "/d", Source: "/dev/sdd", Fstype: "xfs", Major: 253, Minor: 3}, {Root: "/", Mountpoint: "/e", Source: "/dev/sde", Fstype: "zfs", Major: 253, Minor: 4}, }, expected: map[string]partition{ "/dev/sda": {fsType: "ext3", mountpoint: "/a", major: 253, minor: 0}, "/dev/sdb": {fsType: "ext4", mountpoint: "/b", major: 253, minor: 1}, "/dev/sdc": {fsType: "btrfs", mountpoint: "/c", major: 253, minor: 2}, "/dev/sdd": {fsType: "xfs", mountpoint: "/d", major: 253, minor: 3}, "/dev/sde": {fsType: "zfs", mountpoint: "/e", major: 253, minor: 4}, }, }, } for _, test := range tests { actual := processMounts(test.mounts, test.excludedPrefixes) if !reflect.DeepEqual(test.expected, actual) { t.Errorf("%s: expected %#v, got %#v", test.name, test.expected, actual) } } } cadvisor-0.27.1/fs/test_resources/000077500000000000000000000000001315410276000170735ustar00rootroot00000000000000cadvisor-0.27.1/fs/test_resources/diskstats000066400000000000000000000053361315410276000210360ustar00rootroot00000000000000 1 0 ram0 0 0 0 0 0 0 0 0 0 0 0 1 1 ram1 0 0 0 0 0 0 0 0 0 0 0 1 2 ram2 0 0 0 0 0 0 0 0 0 0 0 1 3 ram3 0 0 0 0 0 0 0 0 0 0 0 1 4 ram4 0 0 0 0 0 0 0 0 0 0 0 1 5 ram5 0 0 0 0 0 0 0 0 0 0 0 1 6 ram6 0 0 0 0 0 0 0 0 0 0 0 1 7 ram7 0 0 0 0 0 0 0 0 0 0 0 1 8 ram8 0 0 0 0 0 0 0 0 0 0 0 1 9 ram9 0 0 0 0 0 0 0 0 0 0 0 1 10 ram10 0 0 0 0 0 0 0 0 0 0 0 1 11 ram11 0 0 0 0 0 0 0 0 0 0 0 1 12 ram12 0 0 0 0 0 0 0 0 0 0 0 1 13 ram13 0 0 0 0 0 0 0 0 0 0 0 1 14 ram14 0 0 0 0 0 0 0 0 0 0 0 1 15 ram15 0 0 0 0 0 0 0 0 0 0 0 7 0 loop0 0 0 0 0 0 0 0 0 0 0 0 7 1 loop1 0 0 0 0 0 0 0 0 0 0 0 7 2 loop2 0 0 0 0 0 0 0 0 0 0 0 7 3 loop3 0 0 0 0 0 0 0 0 0 0 0 7 4 loop4 0 0 0 0 0 0 0 0 0 0 0 7 5 loop5 0 0 0 0 0 0 0 0 0 0 0 7 6 loop6 0 0 0 0 0 0 0 0 0 0 0 7 7 loop7 0 0 0 0 0 0 0 0 0 0 0 8 16 sdb 931 1157 7601 960 2 0 16 0 0 919 960 8 17 sdb1 477 1147 3895 271 1 0 8 0 0 271 271 8 18 sdb2 395 0 3154 326 1 0 8 0 0 326 326 8 0 sda 931 1157 7601 1065 2 0 16 0 0 873 1065 8 1 sda1 477 1147 3895 419 1 0 8 0 0 419 419 8 2 sda2 395 0 3154 328 1 0 8 0 0 328 328 8 32 sdc 12390 470 457965 36363 72184 244851 9824537 5359169 0 607738 5437210 8 33 sdc1 10907 221 446193 34366 72173 244851 9824499 5359063 0 606972 5435214 8 34 sdc2 650 249 5120 901 7 0 22 93 0 956 994 8 35 sdc3 264 0 2106 380 1 0 8 0 0 380 380 8 36 sdc4 392 0 3130 476 1 0 8 0 0 475 475 8 48 sdd 3371 134 58909 18327 73997 243043 9824537 4532714 0 594248 4602162 8 49 sdd1 2498 134 51977 17192 73986 243043 9824499 4532600 0 593618 4600885 8 50 sdd2 40 0 280 223 7 0 22 108 0 330 330 8 51 sdd3 264 0 2106 328 1 0 8 0 0 328 328 8 52 sdd4 392 0 3130 373 1 0 8 1 0 374 374 8 64 sde 931 1157 7601 768 2 0 16 0 0 632 768 8 65 sde1 477 1147 3895 252 1 0 8 0 0 252 252 8 66 sde2 395 0 3154 281 1 0 8 0 0 281 281 8 80 sdf 931 1157 7601 936 2 0 16 0 0 717 936 8 81 sdf1 477 1147 3895 382 1 0 8 0 0 382 382 8 82 sdf2 395 0 3154 321 1 0 8 0 0 321 321 8 96 sdg 931 1157 7601 858 2 0 16 0 0 804 858 8 97 sdg1 477 1147 3895 244 1 0 8 0 0 244 244 8 98 sdg2 395 0 3154 299 1 0 8 0 0 299 299 8 112 sdh 931 1157 7601 895 2 0 16 0 0 841 895 8 113 sdh1 477 1147 3895 264 1 0 8 0 0 264 264 8 114 sdh2 395 0 3154 311 1 0 8 0 0 311 311 252 0 dm-0 1251094 0 108121362 21287644 111848 0 52908472 22236936 0 4838500 43524784 252 1 dm-1 58415638 0 2682446960 1719953592 20048040 0 543988240 1975572544 0 262085340 3695556828 cadvisor-0.27.1/fs/types.go000066400000000000000000000053101315410276000155140ustar00rootroot00000000000000// 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 fs import ( "errors" "time" ) type DeviceInfo struct { Device string Major uint Minor uint } type FsType string func (ft FsType) String() string { return string(ft) } const ( ZFS FsType = "zfs" DeviceMapper FsType = "devicemapper" VFS FsType = "vfs" ) type Fs struct { DeviceInfo Type FsType Capacity uint64 Free uint64 Available uint64 Inodes *uint64 InodesFree *uint64 DiskStats DiskStats } type DiskStats struct { ReadsCompleted uint64 ReadsMerged uint64 SectorsRead uint64 ReadTime uint64 WritesCompleted uint64 WritesMerged uint64 SectorsWritten uint64 WriteTime uint64 IoInProgress uint64 IoTime uint64 WeightedIoTime uint64 } // ErrNoSuchDevice is the error indicating the requested device does not exist. var ErrNoSuchDevice = errors.New("cadvisor: no such device") type FsInfo interface { // Returns capacity and free space, in bytes, of all the ext2, ext3, ext4 filesystems on the host. GetGlobalFsInfo() ([]Fs, error) // Returns capacity and free space, in bytes, of the set of mounts passed. GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error) // Returns number of bytes occupied by 'dir'. GetDirDiskUsage(dir string, timeout time.Duration) (uint64, error) // Returns number of inodes used by 'dir'. GetDirInodeUsage(dir string, timeout time.Duration) (uint64, error) // GetDeviceInfoByFsUUID returns the information of the device with the // specified filesystem uuid. If no such device exists, this function will // return the ErrNoSuchDevice error. GetDeviceInfoByFsUUID(uuid string) (*DeviceInfo, error) // Returns the block device info of the filesystem on which 'dir' resides. GetDirFsDevice(dir string) (*DeviceInfo, error) // Returns the device name associated with a particular label. GetDeviceForLabel(label string) (string, error) // Returns all labels associated with a particular device name. GetLabelsForDevice(device string) ([]string, error) // Returns the mountpoint associated with a particular device. GetMountpointForDevice(device string) (string, error) } cadvisor-0.27.1/healthz/000077500000000000000000000000001315410276000150515ustar00rootroot00000000000000cadvisor-0.27.1/healthz/healthz.go000066400000000000000000000017201315410276000170370ustar00rootroot00000000000000// 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 healthz import ( "net/http" httpmux "github.com/google/cadvisor/http/mux" ) func handleHealthz(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) } // Register simple HTTP /healthz handler to return "ok". func RegisterHandler(mux httpmux.Mux) error { mux.HandleFunc("/healthz", handleHealthz) return nil } cadvisor-0.27.1/http/000077500000000000000000000000001315410276000143715ustar00rootroot00000000000000cadvisor-0.27.1/http/handlers.go000066400000000000000000000100571315410276000165230ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package http import ( "fmt" "net/http" "os" "github.com/google/cadvisor/api" "github.com/google/cadvisor/healthz" httpmux "github.com/google/cadvisor/http/mux" "github.com/google/cadvisor/manager" "github.com/google/cadvisor/metrics" "github.com/google/cadvisor/pages" "github.com/google/cadvisor/pages/static" "github.com/google/cadvisor/validate" auth "github.com/abbot/go-http-auth" "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) func RegisterHandlers(mux httpmux.Mux, containerManager manager.Manager, httpAuthFile, httpAuthRealm, httpDigestFile, httpDigestRealm string) error { // Basic health handler. if err := healthz.RegisterHandler(mux); err != nil { return fmt.Errorf("failed to register healthz handler: %s", err) } // Validation/Debug handler. mux.HandleFunc(validate.ValidatePage, func(w http.ResponseWriter, r *http.Request) { err := validate.HandleRequest(w, containerManager) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }) // Register API handler. if err := api.RegisterHandlers(mux, containerManager); err != nil { return fmt.Errorf("failed to register API handlers: %s", err) } // Redirect / to containers page. mux.Handle("/", http.RedirectHandler(pages.ContainersPage, http.StatusTemporaryRedirect)) var authenticated bool // Setup the authenticator object if httpAuthFile != "" { glog.Infof("Using auth file %s", httpAuthFile) secrets := auth.HtpasswdFileProvider(httpAuthFile) authenticator := auth.NewBasicAuthenticator(httpAuthRealm, secrets) mux.HandleFunc(static.StaticResource, authenticator.Wrap(staticHandler)) if err := pages.RegisterHandlersBasic(mux, containerManager, authenticator); err != nil { return fmt.Errorf("failed to register pages auth handlers: %s", err) } authenticated = true } if httpAuthFile == "" && httpDigestFile != "" { glog.Infof("Using digest file %s", httpDigestFile) secrets := auth.HtdigestFileProvider(httpDigestFile) authenticator := auth.NewDigestAuthenticator(httpDigestRealm, secrets) mux.HandleFunc(static.StaticResource, authenticator.Wrap(staticHandler)) if err := pages.RegisterHandlersDigest(mux, containerManager, authenticator); err != nil { return fmt.Errorf("failed to register pages digest handlers: %s", err) } authenticated = true } // Change handler based on authenticator initalization if !authenticated { mux.HandleFunc(static.StaticResource, staticHandlerNoAuth) if err := pages.RegisterHandlersBasic(mux, containerManager, nil); err != nil { return fmt.Errorf("failed to register pages handlers: %s", err) } } return nil } // RegisterPrometheusHandler creates a new PrometheusCollector and configures // the provided HTTP mux to handle the given Prometheus endpoint. func RegisterPrometheusHandler(mux httpmux.Mux, containerManager manager.Manager, prometheusEndpoint string, f metrics.ContainerLabelsFunc) { r := prometheus.NewRegistry() r.MustRegister( metrics.NewPrometheusCollector(containerManager, f), prometheus.NewGoCollector(), prometheus.NewProcessCollector(os.Getpid(), ""), ) mux.Handle(prometheusEndpoint, promhttp.HandlerFor(r, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError})) } func staticHandlerNoAuth(w http.ResponseWriter, r *http.Request) { static.HandleRequest(w, r.URL) } func staticHandler(w http.ResponseWriter, r *auth.AuthenticatedRequest) { static.HandleRequest(w, r.URL) } cadvisor-0.27.1/http/mux/000077500000000000000000000000001315410276000152025ustar00rootroot00000000000000cadvisor-0.27.1/http/mux/mux.go000066400000000000000000000015751315410276000163520ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mux import ( "net/http" ) // Mux interface expected by cAdvisor components. type Mux interface { HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) Handler(r *http.Request) (http.Handler, string) Handle(pattern string, handler http.Handler) } cadvisor-0.27.1/info/000077500000000000000000000000001315410276000143455ustar00rootroot00000000000000cadvisor-0.27.1/info/v1/000077500000000000000000000000001315410276000146735ustar00rootroot00000000000000cadvisor-0.27.1/info/v1/container.go000066400000000000000000000446371315410276000172220ustar00rootroot00000000000000// 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 v1 import ( "reflect" "time" ) type CpuSpec struct { Limit uint64 `json:"limit"` MaxLimit uint64 `json:"max_limit"` Mask string `json:"mask,omitempty"` Quota uint64 `json:"quota,omitempty"` Period uint64 `json:"period,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 { // Time at which the container was created. CreationTime time.Time `json:"creation_time,omitempty"` // Metadata labels associated with this container. Labels map[string]string `json:"labels,omitempty"` // Metadata envs associated with this container. Only whitelisted envs are added. Envs map[string]string `json:"envs,omitempty"` HasCpu bool `json:"has_cpu"` Cpu CpuSpec `json:"cpu,omitempty"` HasMemory bool `json:"has_memory"` Memory MemorySpec `json:"memory,omitempty"` HasNetwork bool `json:"has_network"` HasFilesystem bool `json:"has_filesystem"` // HasDiskIo when true, indicates that DiskIo stats will be available. HasDiskIo bool `json:"has_diskio"` HasCustomMetrics bool `json:"has_custom_metrics"` CustomMetrics []MetricSpec `json:"custom_metrics,omitempty"` // Image name used for this container. Image string `json:"image,omitempty"` } // Container reference contains enough information to uniquely identify a container type ContainerReference struct { // The container id Id string `json:"id,omitempty"` // The absolute name of the container. This is unique on the machine. Name string `json:"name"` // Other names by which the container is known within a certain namespace. // This is unique within that namespace. Aliases []string `json:"aliases,omitempty"` // Namespace under which the aliases of a container are unique. // An example of a namespace is "docker" for Docker containers. Namespace string `json:"namespace,omitempty"` Labels map[string]string `json:"labels,omitempty"` } // Sorts by container name. type ContainerReferenceSlice []ContainerReference func (self ContainerReferenceSlice) Len() int { return len(self) } func (self ContainerReferenceSlice) Swap(i, j int) { self[i], self[j] = self[j], self[i] } func (self ContainerReferenceSlice) Less(i, j int) bool { return self[i].Name < self[j].Name } // ContainerInfoRequest is used when users check a container info from the REST API. // It specifies how much data users want to get about a container type ContainerInfoRequest struct { // Max number of stats to return. Specify -1 for all stats currently available. // Default: 60 NumStats int `json:"num_stats,omitempty"` // Start time for which to query information. // If ommitted, the beginning of time is assumed. Start time.Time `json:"start,omitempty"` // End time for which to query information. // If ommitted, current time is assumed. End time.Time `json:"end,omitempty"` } // Returns a ContainerInfoRequest with all default values specified. func DefaultContainerInfoRequest() ContainerInfoRequest { return ContainerInfoRequest{ NumStats: 60, } } func (self *ContainerInfoRequest) Equals(other ContainerInfoRequest) bool { return self.NumStats == other.NumStats && self.Start.Equal(other.Start) && self.End.Equal(other.End) } 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"` } // TODO(vmarmol): Refactor to not need this equality comparison. // ContainerInfo may be (un)marshaled by json or other en/decoder. In that // case, the Timestamp field in each stats/sample may not be precisely // en/decoded. This will lead to small but acceptable differences between a // ContainerInfo and its encode-then-decode version. Eq() is used to compare // two ContainerInfo accepting small difference (<10ms) of Time fields. func (self *ContainerInfo) Eq(b *ContainerInfo) bool { // If both self and b are nil, then Eq() returns true if self == nil { return b == nil } if b == nil { return self == nil } // For fields other than time.Time, we will compare them precisely. // This would require that any slice should have same order. if !reflect.DeepEqual(self.ContainerReference, b.ContainerReference) { return false } if !reflect.DeepEqual(self.Subcontainers, b.Subcontainers) { return false } if !self.Spec.Eq(&b.Spec) { return false } for i, expectedStats := range b.Stats { selfStats := self.Stats[i] if !expectedStats.Eq(selfStats) { return false } } return true } func (self *ContainerSpec) Eq(b *ContainerSpec) bool { // Creation within 1s of each other. diff := self.CreationTime.Sub(b.CreationTime) if (diff > time.Second) || (diff < -time.Second) { return false } if self.HasCpu != b.HasCpu { return false } if !reflect.DeepEqual(self.Cpu, b.Cpu) { return false } if self.HasMemory != b.HasMemory { return false } if !reflect.DeepEqual(self.Memory, b.Memory) { return false } if self.HasNetwork != b.HasNetwork { return false } if self.HasFilesystem != b.HasFilesystem { return false } if self.HasDiskIo != b.HasDiskIo { return false } if self.HasCustomMetrics != b.HasCustomMetrics { return false } return true } 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 } // This mirrors kernel internal structure. type LoadStats struct { // Number of sleeping tasks. NrSleeping uint64 `json:"nr_sleeping"` // Number of running tasks. NrRunning uint64 `json:"nr_running"` // Number of tasks in stopped state NrStopped uint64 `json:"nr_stopped"` // Number of tasks in uninterruptible state NrUninterruptible uint64 `json:"nr_uninterruptible"` // Number of tasks waiting on IO NrIoWait uint64 `json:"nr_io_wait"` } // CPU usage time statistics. type CpuUsage struct { // Total CPU usage. // Unit: nanoseconds. Total uint64 `json:"total"` // Per CPU/core usage of the container. // Unit: nanoseconds. PerCpu []uint64 `json:"per_cpu_usage,omitempty"` // Time spent in user space. // Unit: nanoseconds. User uint64 `json:"user"` // Time spent in kernel space. // Unit: nanoseconds. System uint64 `json:"system"` } // Cpu Completely Fair Scheduler statistics. type CpuCFS struct { // Total number of elapsed enforcement intervals. Periods uint64 `json:"periods"` // Total number of times tasks in the cgroup have been throttled. ThrottledPeriods uint64 `json:"throttled_periods"` // Total time duration for which tasks in the cgroup have been throttled. // Unit: nanoseconds. ThrottledTime uint64 `json:"throttled_time"` } // All CPU usage metrics are cumulative from the creation of the container type CpuStats struct { Usage CpuUsage `json:"usage"` CFS CpuCFS `json:"cfs"` // Smoothed average of number of runnable threads x 1000. // We multiply by thousand to avoid using floats, but preserving precision. // Load is smoothed over the last 10 seconds. Instantaneous value can be read // from LoadStats.NrRunning. LoadAverage int32 `json:"load_average"` } type PerDiskStats struct { Device string `json:"-"` Major uint64 `json:"major"` Minor uint64 `json:"minor"` Stats map[string]uint64 `json:"stats"` } type DiskIoStats struct { IoServiceBytes []PerDiskStats `json:"io_service_bytes,omitempty"` IoServiced []PerDiskStats `json:"io_serviced,omitempty"` IoQueued []PerDiskStats `json:"io_queued,omitempty"` Sectors []PerDiskStats `json:"sectors,omitempty"` IoServiceTime []PerDiskStats `json:"io_service_time,omitempty"` IoWaitTime []PerDiskStats `json:"io_wait_time,omitempty"` IoMerged []PerDiskStats `json:"io_merged,omitempty"` IoTime []PerDiskStats `json:"io_time,omitempty"` } type MemoryStats struct { // Current memory usage, this includes all memory regardless of when it was // accessed. // Units: Bytes. Usage uint64 `json:"usage"` // Number of bytes of page cache memory. // Units: Bytes. Cache uint64 `json:"cache"` // The amount of anonymous and swap cache memory (includes transparent // hugepages). // Units: Bytes. RSS uint64 `json:"rss"` // The amount of swap currently used by the processes in this cgroup // Units: Bytes. Swap uint64 `json:"swap"` // 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"` Failcnt uint64 `json:"failcnt"` ContainerData MemoryStatsMemoryData `json:"container_data,omitempty"` HierarchicalData MemoryStatsMemoryData `json:"hierarchical_data,omitempty"` } type MemoryStatsMemoryData struct { Pgfault uint64 `json:"pgfault"` Pgmajfault uint64 `json:"pgmajfault"` } type InterfaceStats struct { // The name of the interface. Name string `json:"name"` // Cumulative count of bytes received. RxBytes uint64 `json:"rx_bytes"` // Cumulative count of packets received. RxPackets uint64 `json:"rx_packets"` // Cumulative count of receive errors encountered. RxErrors uint64 `json:"rx_errors"` // Cumulative count of packets dropped while receiving. RxDropped uint64 `json:"rx_dropped"` // Cumulative count of bytes transmitted. TxBytes uint64 `json:"tx_bytes"` // Cumulative count of packets transmitted. TxPackets uint64 `json:"tx_packets"` // Cumulative count of transmit errors encountered. TxErrors uint64 `json:"tx_errors"` // Cumulative count of packets dropped while transmitting. TxDropped uint64 `json:"tx_dropped"` } type NetworkStats struct { InterfaceStats `json:",inline"` Interfaces []InterfaceStats `json:"interfaces,omitempty"` // TCP connection stats (Established, Listen...) Tcp TcpStat `json:"tcp"` // TCP6 connection stats (Established, Listen...) Tcp6 TcpStat `json:"tcp6"` // UDP connection stats Udp UdpStat `json:"udp"` // UDP6 connection stats Udp6 UdpStat `json:"udp6"` } type TcpStat struct { // Count of TCP connections in state "Established" Established uint64 // Count of TCP connections in state "Syn_Sent" SynSent uint64 // Count of TCP connections in state "Syn_Recv" SynRecv uint64 // Count of TCP connections in state "Fin_Wait1" FinWait1 uint64 // Count of TCP connections in state "Fin_Wait2" FinWait2 uint64 // Count of TCP connections in state "Time_Wait TimeWait uint64 // Count of TCP connections in state "Close" Close uint64 // Count of TCP connections in state "Close_Wait" CloseWait uint64 // Count of TCP connections in state "Listen_Ack" LastAck uint64 // Count of TCP connections in state "Listen" Listen uint64 // Count of TCP connections in state "Closing" Closing uint64 } type UdpStat struct { // Count of UDP sockets in state "Listen" Listen uint64 // Count of UDP packets dropped by the IP stack Dropped uint64 // Count of packets Queued for Receieve RxQueued uint64 // Count of packets Queued for Transmit TxQueued uint64 } type FsStats struct { // The block device name associated with the filesystem. Device string `json:"device,omitempty"` // Type of the filesytem. Type string `json:"type"` // Number of bytes that can be consumed by the container on this filesystem. Limit uint64 `json:"capacity"` // Number of bytes that is consumed by the container on this filesystem. Usage uint64 `json:"usage"` // Base Usage that is consumed by the container's writable layer. // This field is only applicable for docker container's as of now. BaseUsage uint64 `json:"base_usage"` // Number of bytes available for non-root user. Available uint64 `json:"available"` // HasInodes when true, indicates that Inodes info will be available. HasInodes bool `json:"has_inodes"` // Number of Inodes Inodes uint64 `json:"inodes"` // Number of available Inodes InodesFree uint64 `json:"inodes_free"` // Number of reads completed // This is the total number of reads completed successfully. ReadsCompleted uint64 `json:"reads_completed"` // Number of reads merged // Reads and writes which are adjacent to each other may be merged for // efficiency. Thus two 4K reads may become one 8K read before it is // ultimately handed to the disk, and so it will be counted (and queued) // as only one I/O. This field lets you know how often this was done. ReadsMerged uint64 `json:"reads_merged"` // Number of sectors read // This is the total number of sectors read successfully. SectorsRead uint64 `json:"sectors_read"` // Number of milliseconds spent reading // This is the total number of milliseconds spent by all reads (as // measured from __make_request() to end_that_request_last()). ReadTime uint64 `json:"read_time"` // Number of writes completed // This is the total number of writes completed successfully. WritesCompleted uint64 `json:"writes_completed"` // Number of writes merged // See the description of reads merged. WritesMerged uint64 `json:"writes_merged"` // Number of sectors written // This is the total number of sectors written successfully. SectorsWritten uint64 `json:"sectors_written"` // Number of milliseconds spent writing // This is the total number of milliseconds spent by all writes (as // measured from __make_request() to end_that_request_last()). WriteTime uint64 `json:"write_time"` // Number of I/Os currently in progress // The only field that should go to zero. Incremented as requests are // given to appropriate struct request_queue and decremented as they finish. IoInProgress uint64 `json:"io_in_progress"` // Number of milliseconds spent doing I/Os // This field increases so long as field 9 is nonzero. IoTime uint64 `json:"io_time"` // weighted number of milliseconds spent doing I/Os // This field is incremented at each I/O start, I/O completion, I/O // merge, or read of these stats by the number of I/Os in progress // (field 9) times the number of milliseconds spent doing I/O since the // last update of this field. This can provide an easy measure of both // I/O completion time and the backlog that may be accumulating. WeightedIoTime uint64 `json:"weighted_io_time"` } type ContainerStats struct { // The time of this stat point. Timestamp time.Time `json:"timestamp"` Cpu CpuStats `json:"cpu,omitempty"` DiskIo DiskIoStats `json:"diskio,omitempty"` Memory MemoryStats `json:"memory,omitempty"` Network NetworkStats `json:"network,omitempty"` // Filesystem statistics Filesystem []FsStats `json:"filesystem,omitempty"` // Task load stats TaskStats LoadStats `json:"task_stats,omitempty"` // Custom metrics from all collectors CustomMetrics map[string][]MetricVal `json:"custom_metrics,omitempty"` } func timeEq(t1, t2 time.Time, tolerance time.Duration) bool { // t1 should not be later than t2 if t1.After(t2) { t1, t2 = t2, t1 } diff := t2.Sub(t1) if diff <= tolerance { return true } return false } const ( // 10ms, i.e. 0.01s timePrecision time.Duration = 10 * time.Millisecond ) // This function is useful because we do not require precise time // representation. func (a *ContainerStats) Eq(b *ContainerStats) bool { if !timeEq(a.Timestamp, b.Timestamp, timePrecision) { return false } return a.StatsEq(b) } // Checks equality of the stats values. func (a *ContainerStats) StatsEq(b *ContainerStats) bool { // TODO(vmarmol): Consider using this through reflection. if !reflect.DeepEqual(a.Cpu, b.Cpu) { return false } if !reflect.DeepEqual(a.Memory, b.Memory) { return false } if !reflect.DeepEqual(a.DiskIo, b.DiskIo) { return false } if !reflect.DeepEqual(a.Network, b.Network) { return false } if !reflect.DeepEqual(a.Filesystem, b.Filesystem) { return false } return true } // Event contains information general to events such as the time at which they // occurred, their specific type, and the actual event. Event types are // differentiated by the EventType field of Event. type Event struct { // the absolute container name for which the event occurred ContainerName string `json:"container_name"` // the time at which the event occurred Timestamp time.Time `json:"timestamp"` // the type of event. EventType is an enumerated type EventType EventType `json:"event_type"` // the original event object and all of its extraneous data, ex. an // OomInstance EventData EventData `json:"event_data,omitempty"` } // EventType is an enumerated type which lists the categories under which // events may fall. The Event field EventType is populated by this enum. type EventType string const ( EventOom EventType = "oom" EventOomKill = "oomKill" EventContainerCreation = "containerCreation" EventContainerDeletion = "containerDeletion" ) // Extra information about an event. Only one type will be set. type EventData struct { // Information about an OOM kill event. OomKill *OomKillEventData `json:"oom,omitempty"` } // Information related to an OOM kill instance type OomKillEventData struct { // process id of the killed process Pid int `json:"pid"` // The name of the killed process ProcessName string `json:"process_name"` } cadvisor-0.27.1/info/v1/container_test.go000066400000000000000000000037201315410276000202450ustar00rootroot00000000000000// 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 v1 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 createStats(cpuUsage, memUsage uint64, timestamp time.Time) *ContainerStats { stats := &ContainerStats{} 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 } cadvisor-0.27.1/info/v1/docker.go000066400000000000000000000030141315410276000164670ustar00rootroot00000000000000// Copyright 2016 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. // Types used for docker containers. package v1 type DockerStatus struct { Version string `json:"version"` APIVersion string `json:"api_version"` KernelVersion string `json:"kernel_version"` OS string `json:"os"` Hostname string `json:"hostname"` RootDir string `json:"root_dir"` Driver string `json:"driver"` DriverStatus map[string]string `json:"driver_status"` ExecDriver string `json:"exec_driver"` NumImages int `json:"num_images"` NumContainers int `json:"num_containers"` } type DockerImage struct { ID string `json:"id"` RepoTags []string `json:"repo_tags"` // repository name and tags. Created int64 `json:"created"` // unix time since creation. VirtualSize int64 `json:"virtual_size"` Size int64 `json:"size"` } cadvisor-0.27.1/info/v1/machine.go000066400000000000000000000127651315410276000166410ustar00rootroot00000000000000// 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 v1 type FsInfo struct { // Block device associated with the filesystem. Device string `json:"device"` // DeviceMajor is the major identifier of the device, used for correlation with blkio stats DeviceMajor uint64 `json:"-"` // DeviceMinor is the minor identifier of the device, used for correlation with blkio stats DeviceMinor uint64 `json:"-"` // Total number of bytes available on the filesystem. Capacity uint64 `json:"capacity"` // Type of device. Type string `json:"type"` // Total number of inodes available on the filesystem. Inodes uint64 `json:"inodes"` // HasInodes when true, indicates that Inodes info will be available. HasInodes bool `json:"has_inodes"` } type Node struct { Id int `json:"node_id"` // Per-node memory Memory uint64 `json:"memory"` Cores []Core `json:"cores"` Caches []Cache `json:"caches"` } type Core struct { Id int `json:"core_id"` Threads []int `json:"thread_ids"` Caches []Cache `json:"caches"` } type Cache struct { // Size of memory cache in bytes. Size uint64 `json:"size"` // Type of memory cache: data, instruction, or unified. Type string `json:"type"` // Level (distance from cpus) in a multi-level cache hierarchy. Level int `json:"level"` } func (self *Node) FindCore(id int) (bool, int) { for i, n := range self.Cores { if n.Id == id { return true, i } } return false, -1 } func (self *Node) AddThread(thread int, core int) { var coreIdx int if core == -1 { // Assume one hyperthread per core when topology data is missing. core = thread } ok, coreIdx := self.FindCore(core) if !ok { // New core core := Core{Id: core} self.Cores = append(self.Cores, core) coreIdx = len(self.Cores) - 1 } self.Cores[coreIdx].Threads = append(self.Cores[coreIdx].Threads, thread) } func (self *Node) AddNodeCache(c Cache) { self.Caches = append(self.Caches, c) } func (self *Node) AddPerCoreCache(c Cache) { for idx := range self.Cores { self.Cores[idx].Caches = append(self.Cores[idx].Caches, c) } } type HugePagesInfo struct { // huge page size (in kB) PageSize uint64 `json:"page_size"` // number of huge pages NumPages uint64 `json:"num_pages"` } type DiskInfo struct { // device name Name string `json:"name"` // Major number Major uint64 `json:"major"` // Minor number Minor uint64 `json:"minor"` // Size in bytes Size uint64 `json:"size"` // I/O Scheduler - one of "none", "noop", "cfq", "deadline" Scheduler string `json:"scheduler"` } type NetInfo struct { // Device name Name string `json:"name"` // Mac Address MacAddress string `json:"mac_address"` // Speed in MBits/s Speed int64 `json:"speed"` // Maximum Transmission Unit Mtu int64 `json:"mtu"` } type CloudProvider string const ( GCE CloudProvider = "GCE" AWS = "AWS" Azure = "Azure" Baremetal = "Baremetal" UnknownProvider = "Unknown" ) type InstanceType string const ( NoInstance InstanceType = "None" UnknownInstance = "Unknown" ) type InstanceID string const ( UnNamedInstance InstanceID = "None" ) type MachineInfo struct { // The number of cores in this machine. NumCores int `json:"num_cores"` // Maximum clock speed for the cores, in KHz. CpuFrequency uint64 `json:"cpu_frequency_khz"` // The amount of memory (in bytes) in this machine MemoryCapacity uint64 `json:"memory_capacity"` // HugePages on this machine. HugePages []HugePagesInfo `json:"hugepages"` // The machine id MachineID string `json:"machine_id"` // The system uuid SystemUUID string `json:"system_uuid"` // The boot id BootID string `json:"boot_id"` // Filesystems on this machine. Filesystems []FsInfo `json:"filesystems"` // Disk map DiskMap map[string]DiskInfo `json:"disk_map"` // Network devices NetworkDevices []NetInfo `json:"network_devices"` // Machine Topology // Describes cpu/memory layout and hierarchy. Topology []Node `json:"topology"` // Cloud provider the machine belongs to. CloudProvider CloudProvider `json:"cloud_provider"` // Type of cloud instance (e.g. GCE standard) the machine is. InstanceType InstanceType `json:"instance_type"` // ID of cloud instance (e.g. instance-1) given to it by the cloud provider. InstanceID InstanceID `json:"instance_id"` } 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"` // Docker API Version DockerAPIVersion string `json:"docker_api_version"` // cAdvisor version. CadvisorVersion string `json:"cadvisor_version"` // cAdvisor git revision. CadvisorRevision string `json:"cadvisor_revision"` } type MachineInfoFactory interface { GetMachineInfo() (*MachineInfo, error) GetVersionInfo() (*VersionInfo, error) } cadvisor-0.27.1/info/v1/metric.go000066400000000000000000000037401315410276000165110ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v1 import ( "time" ) // Type of metric being exported. type MetricType string const ( // Instantaneous value. May increase or decrease. MetricGauge MetricType = "gauge" // A counter-like value that is only expected to increase. MetricCumulative MetricType = "cumulative" // Rate over a time period. MetricDelta MetricType = "delta" ) // DataType for metric being exported. type DataType string const ( IntType DataType = "int" FloatType DataType = "float" ) // Spec for custom metric. type MetricSpec struct { // The name of the metric. Name string `json:"name"` // Type of the metric. Type MetricType `json:"type"` // Data Type for the stats. Format DataType `json:"format"` // Display Units for the stats. Units string `json:"units"` } // An exported metric. type MetricValBasic struct { // Time at which the metric was queried Timestamp time.Time `json:"timestamp"` // The value of the metric at this point. IntValue int64 `json:"int_value,omitempty"` FloatValue float64 `json:"float_value,omitempty"` } // An exported metric. type MetricVal struct { // Label associated with a metric Label string `json:"label,omitempty"` // Time at which the metric was queried Timestamp time.Time `json:"timestamp"` // The value of the metric at this point. IntValue int64 `json:"int_value,omitempty"` FloatValue float64 `json:"float_value,omitempty"` } cadvisor-0.27.1/info/v1/test/000077500000000000000000000000001315410276000156525ustar00rootroot00000000000000cadvisor-0.27.1/info/v1/test/datagen.go000066400000000000000000000047151315410276000176130ustar00rootroot00000000000000// 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 ( "fmt" "math/rand" "time" info "github.com/google/cadvisor/info/v1" ) func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info.ContainerStats { ret := make([]*info.ContainerStats, numStats) perCoreUsages := make([]uint64, numCores) currentTime := time.Now() for i := range perCoreUsages { perCoreUsages[i] = uint64(rand.Int63n(1000)) } for i := 0; i < numStats; i++ { stats := new(info.ContainerStats) stats.Timestamp = currentTime currentTime = currentTime.Add(duration) percore := make([]uint64, numCores) for i := range perCoreUsages { perCoreUsages[i] += uint64(rand.Int63n(1000)) percore[i] = perCoreUsages[i] stats.Cpu.Usage.Total += percore[i] } stats.Cpu.Usage.PerCpu = percore stats.Cpu.Usage.User = stats.Cpu.Usage.Total stats.Cpu.Usage.System = 0 stats.Memory.Usage = uint64(rand.Int63n(4096)) stats.Memory.Cache = uint64(rand.Int63n(4096)) stats.Memory.RSS = uint64(rand.Int63n(4096)) ret[i] = stats } return ret } func GenerateRandomContainerSpec(numCores int) info.ContainerSpec { ret := info.ContainerSpec{ CreationTime: time.Now(), HasCpu: true, Cpu: info.CpuSpec{}, HasMemory: true, Memory: info.MemorySpec{}, } ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000)) ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000)) ret.Cpu.Mask = fmt.Sprintf("0-%d", numCores-1) ret.Memory.Limit = uint64(4096 + rand.Int63n(4096)) return ret } func GenerateRandomContainerInfo(containerName string, numCores int, query *info.ContainerInfoRequest, duration time.Duration) *info.ContainerInfo { stats := GenerateRandomStats(query.NumStats, numCores, duration) spec := GenerateRandomContainerSpec(numCores) ret := &info.ContainerInfo{ ContainerReference: info.ContainerReference{ Name: containerName, }, Spec: spec, Stats: stats, } return ret } cadvisor-0.27.1/info/v2/000077500000000000000000000000001315410276000146745ustar00rootroot00000000000000cadvisor-0.27.1/info/v2/container.go000066400000000000000000000234321315410276000172110ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v2 import ( "time" // TODO(rjnagal): Remove dependency after moving all stats structs from v1. // using v1 now for easy conversion. "github.com/google/cadvisor/info/v1" ) const ( TypeName = "name" TypeDocker = "docker" ) type CpuSpec struct { // Requested cpu shares. Default is 1024. Limit uint64 `json:"limit"` // Requested cpu hard limit. Default is unlimited (0). // Units: milli-cpus. MaxLimit uint64 `json:"max_limit"` // Cpu affinity mask. // TODO(rjnagal): Add a library to convert mask string to set of cpu bitmask. Mask string `json:"mask,omitempty"` // CPUQuota Default is disabled Quota uint64 `json:"quota,omitempty"` // Period is the CPU reference time in ns e.g the quota is compared aginst this. Period uint64 `json:"period,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 ContainerInfo struct { // Describes the container. Spec ContainerSpec `json:"spec,omitempty"` // Historical statistics gathered from the container. Stats []*ContainerStats `json:"stats,omitempty"` } type ContainerSpec struct { // Time at which the container was created. CreationTime time.Time `json:"creation_time,omitempty"` // Other names by which the container is known within a certain namespace. // This is unique within that namespace. Aliases []string `json:"aliases,omitempty"` // Namespace under which the aliases of a container are unique. // An example of a namespace is "docker" for Docker containers. Namespace string `json:"namespace,omitempty"` // Metadata labels associated with this container. Labels map[string]string `json:"labels,omitempty"` // Metadata envs associated with this container. Only whitelisted envs are added. Envs map[string]string `json:"envs,omitempty"` HasCpu bool `json:"has_cpu"` Cpu CpuSpec `json:"cpu,omitempty"` HasMemory bool `json:"has_memory"` Memory MemorySpec `json:"memory,omitempty"` HasCustomMetrics bool `json:"has_custom_metrics"` CustomMetrics []v1.MetricSpec `json:"custom_metrics,omitempty"` // Following resources have no associated spec, but are being isolated. HasNetwork bool `json:"has_network"` HasFilesystem bool `json:"has_filesystem"` HasDiskIo bool `json:"has_diskio"` // Image name used for this container. Image string `json:"image,omitempty"` } type DeprecatedContainerStats struct { // The time of this stat point. Timestamp time.Time `json:"timestamp"` // CPU statistics HasCpu bool `json:"has_cpu"` // In nanoseconds (aggregated) Cpu v1.CpuStats `json:"cpu,omitempty"` // In nanocores per second (instantaneous) CpuInst *CpuInstStats `json:"cpu_inst,omitempty"` // Disk IO statistics HasDiskIo bool `json:"has_diskio"` DiskIo v1.DiskIoStats `json:"diskio,omitempty"` // Memory statistics HasMemory bool `json:"has_memory"` Memory v1.MemoryStats `json:"memory,omitempty"` // Network statistics HasNetwork bool `json:"has_network"` Network NetworkStats `json:"network,omitempty"` // Filesystem statistics HasFilesystem bool `json:"has_filesystem"` Filesystem []v1.FsStats `json:"filesystem,omitempty"` // Task load statistics HasLoad bool `json:"has_load"` Load v1.LoadStats `json:"load_stats,omitempty"` // Custom Metrics HasCustomMetrics bool `json:"has_custom_metrics"` CustomMetrics map[string][]v1.MetricVal `json:"custom_metrics,omitempty"` } type ContainerStats struct { // The time of this stat point. Timestamp time.Time `json:"timestamp"` // CPU statistics // In nanoseconds (aggregated) Cpu *v1.CpuStats `json:"cpu,omitempty"` // In nanocores per second (instantaneous) CpuInst *CpuInstStats `json:"cpu_inst,omitempty"` // Disk IO statistics DiskIo *v1.DiskIoStats `json:"diskio,omitempty"` // Memory statistics Memory *v1.MemoryStats `json:"memory,omitempty"` // Network statistics Network *NetworkStats `json:"network,omitempty"` // Filesystem statistics Filesystem *FilesystemStats `json:"filesystem,omitempty"` // Task load statistics Load *v1.LoadStats `json:"load_stats,omitempty"` // Custom Metrics CustomMetrics map[string][]v1.MetricVal `json:"custom_metrics,omitempty"` } type Percentiles struct { // Indicates whether the stats are present or not. // If true, values below do not have any data. Present bool `json:"present"` // Average over the collected sample. Mean uint64 `json:"mean"` // Max seen over the collected sample. Max uint64 `json:"max"` // 50th percentile over the collected sample. Fifty uint64 `json:"fifty"` // 90th percentile over the collected sample. Ninety uint64 `json:"ninety"` // 95th percentile over the collected sample. NinetyFive uint64 `json:"ninetyfive"` } type Usage struct { // Indicates amount of data available [0-100]. // If we have data for half a day, we'll still process DayUsage, // but set PercentComplete to 50. PercentComplete int32 `json:"percent_complete"` // Mean, Max, and 90p cpu rate value in milliCpus/seconds. Converted to milliCpus to avoid floats. Cpu Percentiles `json:"cpu"` // Mean, Max, and 90p memory size in bytes. Memory Percentiles `json:"memory"` } // latest sample collected for a container. type InstantUsage struct { // cpu rate in cpu milliseconds/second. Cpu uint64 `json:"cpu"` // Memory usage in bytes. Memory uint64 `json:"memory"` } type DerivedStats struct { // Time of generation of these stats. Timestamp time.Time `json:"timestamp"` // Latest instantaneous sample. LatestUsage InstantUsage `json:"latest_usage"` // Percentiles in last observed minute. MinuteUsage Usage `json:"minute_usage"` // Percentile in last hour. HourUsage Usage `json:"hour_usage"` // Percentile in last day. DayUsage Usage `json:"day_usage"` } type FsInfo struct { // Time of generation of these stats. Timestamp time.Time `json:"timestamp"` // The block device name associated with the filesystem. Device string `json:"device"` // Path where the filesystem is mounted. Mountpoint string `json:"mountpoint"` // Filesystem usage in bytes. Capacity uint64 `json:"capacity"` // Bytes available for non-root use. Available uint64 `json:"available"` // Number of bytes used on this filesystem. Usage uint64 `json:"usage"` // Labels associated with this filesystem. Labels []string `json:"labels"` // Number of Inodes. Inodes *uint64 `json:"inodes,omitempty"` // Number of available Inodes (if known) InodesFree *uint64 `json:"inodes_free,omitempty"` } type RequestOptions struct { // Type of container identifier specified - "name", "dockerid", dockeralias" IdType string `json:"type"` // Number of stats to return Count int `json:"count"` // Whether to include stats for child subcontainers. Recursive bool `json:"recursive"` } type ProcessInfo struct { User string `json:"user"` Pid int `json:"pid"` Ppid int `json:"parent_pid"` StartTime string `json:"start_time"` PercentCpu float32 `json:"percent_cpu"` PercentMemory float32 `json:"percent_mem"` RSS uint64 `json:"rss"` VirtualSize uint64 `json:"virtual_size"` Status string `json:"status"` RunningTime string `json:"running_time"` CgroupPath string `json:"cgroup_path"` Cmd string `json:"cmd"` } type TcpStat struct { Established uint64 SynSent uint64 SynRecv uint64 FinWait1 uint64 FinWait2 uint64 TimeWait uint64 Close uint64 CloseWait uint64 LastAck uint64 Listen uint64 Closing uint64 } type NetworkStats struct { // Network stats by interface. Interfaces []v1.InterfaceStats `json:"interfaces,omitempty"` // TCP connection stats (Established, Listen...) Tcp TcpStat `json:"tcp"` // TCP6 connection stats (Established, Listen...) Tcp6 TcpStat `json:"tcp6"` // UDP connection stats Udp v1.UdpStat `json:"udp"` // UDP6 connection stats Udp6 v1.UdpStat `json:"udp6"` } // Instantaneous CPU stats type CpuInstStats struct { Usage CpuInstUsage `json:"usage"` } // CPU usage time statistics. type CpuInstUsage struct { // Total CPU usage. // Units: nanocores per second Total uint64 `json:"total"` // Per CPU/core usage of the container. // Unit: nanocores per second PerCpu []uint64 `json:"per_cpu_usage,omitempty"` // Time spent in user space. // Unit: nanocores per second User uint64 `json:"user"` // Time spent in kernel space. // Unit: nanocores per second System uint64 `json:"system"` } // Filesystem usage statistics. type FilesystemStats struct { // Total Number of bytes consumed by container. TotalUsageBytes *uint64 `json:"totalUsageBytes,omitempty"` // Number of bytes consumed by a container through its root filesystem. BaseUsageBytes *uint64 `json:"baseUsageBytes,omitempty"` // Number of inodes used within the container's root filesystem. // This only accounts for inodes that are shared across containers, // and does not include inodes used in mounted directories. InodeUsage *uint64 `json:"containter_inode_usage,omitempty"` } cadvisor-0.27.1/info/v2/conversion.go000066400000000000000000000201211315410276000174040ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v2 import ( "fmt" "time" "github.com/golang/glog" "github.com/google/cadvisor/info/v1" ) func machineFsStatsFromV1(fsStats []v1.FsStats) []MachineFsStats { var result []MachineFsStats for i := range fsStats { stat := fsStats[i] readDuration := time.Millisecond * time.Duration(stat.ReadTime) writeDuration := time.Millisecond * time.Duration(stat.WriteTime) ioDuration := time.Millisecond * time.Duration(stat.IoTime) weightedDuration := time.Millisecond * time.Duration(stat.WeightedIoTime) machineFsStat := MachineFsStats{ Device: stat.Device, Type: stat.Type, Capacity: &stat.Limit, Usage: &stat.Usage, Available: &stat.Available, DiskStats: DiskStats{ ReadsCompleted: &stat.ReadsCompleted, ReadsMerged: &stat.ReadsMerged, SectorsRead: &stat.SectorsRead, ReadDuration: &readDuration, WritesCompleted: &stat.WritesCompleted, WritesMerged: &stat.WritesMerged, SectorsWritten: &stat.SectorsWritten, WriteDuration: &writeDuration, IoInProgress: &stat.IoInProgress, IoDuration: &ioDuration, WeightedIoDuration: &weightedDuration, }, } if stat.HasInodes { machineFsStat.InodesFree = &stat.InodesFree } result = append(result, machineFsStat) } return result } func MachineStatsFromV1(cont *v1.ContainerInfo) []MachineStats { var stats []MachineStats var last *v1.ContainerStats for i := range cont.Stats { val := cont.Stats[i] stat := MachineStats{ Timestamp: val.Timestamp, } if cont.Spec.HasCpu { stat.Cpu = &val.Cpu cpuInst, err := InstCpuStats(last, val) if err != nil { glog.Warningf("Could not get instant cpu stats: %v", err) } else { stat.CpuInst = cpuInst } last = val } if cont.Spec.HasMemory { stat.Memory = &val.Memory } if cont.Spec.HasNetwork { stat.Network = &NetworkStats{ // FIXME: Use reflection instead. Tcp: TcpStat(val.Network.Tcp), Tcp6: TcpStat(val.Network.Tcp6), Interfaces: val.Network.Interfaces, } } if cont.Spec.HasFilesystem { stat.Filesystem = machineFsStatsFromV1(val.Filesystem) } // TODO(rjnagal): Handle load stats. stats = append(stats, stat) } return stats } func ContainerStatsFromV1(containerName string, spec *v1.ContainerSpec, stats []*v1.ContainerStats) []*ContainerStats { newStats := make([]*ContainerStats, 0, len(stats)) var last *v1.ContainerStats for _, val := range stats { stat := &ContainerStats{ Timestamp: val.Timestamp, } if spec.HasCpu { stat.Cpu = &val.Cpu cpuInst, err := InstCpuStats(last, val) if err != nil { glog.Warningf("Could not get instant cpu stats: %v", err) } else { stat.CpuInst = cpuInst } last = val } if spec.HasMemory { stat.Memory = &val.Memory } if spec.HasNetwork { // TODO: Handle TcpStats stat.Network = &NetworkStats{ Tcp: TcpStat(val.Network.Tcp), Tcp6: TcpStat(val.Network.Tcp6), Interfaces: val.Network.Interfaces, } } if spec.HasFilesystem { if len(val.Filesystem) == 1 { stat.Filesystem = &FilesystemStats{ TotalUsageBytes: &val.Filesystem[0].Usage, BaseUsageBytes: &val.Filesystem[0].BaseUsage, InodeUsage: &val.Filesystem[0].Inodes, } } else if len(val.Filesystem) > 1 && containerName != "/" { // Cannot handle multiple devices per container. glog.V(4).Infof("failed to handle multiple devices for container %s. Skipping Filesystem stats", containerName) } } if spec.HasDiskIo { stat.DiskIo = &val.DiskIo } if spec.HasCustomMetrics { stat.CustomMetrics = val.CustomMetrics } // TODO(rjnagal): Handle load stats. newStats = append(newStats, stat) } return newStats } func DeprecatedStatsFromV1(cont *v1.ContainerInfo) []DeprecatedContainerStats { stats := make([]DeprecatedContainerStats, 0, len(cont.Stats)) var last *v1.ContainerStats for _, val := range cont.Stats { stat := DeprecatedContainerStats{ Timestamp: val.Timestamp, HasCpu: cont.Spec.HasCpu, HasMemory: cont.Spec.HasMemory, HasNetwork: cont.Spec.HasNetwork, HasFilesystem: cont.Spec.HasFilesystem, HasDiskIo: cont.Spec.HasDiskIo, HasCustomMetrics: cont.Spec.HasCustomMetrics, } if stat.HasCpu { stat.Cpu = val.Cpu cpuInst, err := InstCpuStats(last, val) if err != nil { glog.Warningf("Could not get instant cpu stats: %v", err) } else { stat.CpuInst = cpuInst } last = val } if stat.HasMemory { stat.Memory = val.Memory } if stat.HasNetwork { stat.Network.Interfaces = val.Network.Interfaces } if stat.HasFilesystem { stat.Filesystem = val.Filesystem } if stat.HasDiskIo { stat.DiskIo = val.DiskIo } if stat.HasCustomMetrics { stat.CustomMetrics = val.CustomMetrics } // TODO(rjnagal): Handle load stats. stats = append(stats, stat) } return stats } func InstCpuStats(last, cur *v1.ContainerStats) (*CpuInstStats, error) { if last == nil { return nil, nil } if !cur.Timestamp.After(last.Timestamp) { return nil, fmt.Errorf("container stats move backwards in time") } if len(last.Cpu.Usage.PerCpu) != len(cur.Cpu.Usage.PerCpu) { return nil, fmt.Errorf("different number of cpus") } timeDelta := cur.Timestamp.Sub(last.Timestamp) if timeDelta <= 100*time.Millisecond { return nil, fmt.Errorf("time delta unexpectedly small") } // Nanoseconds to gain precision and avoid having zero seconds if the // difference between the timestamps is just under a second timeDeltaNs := uint64(timeDelta.Nanoseconds()) convertToRate := func(lastValue, curValue uint64) (uint64, error) { if curValue < lastValue { return 0, fmt.Errorf("cumulative stats decrease") } valueDelta := curValue - lastValue // Use float64 to keep precision return uint64(float64(valueDelta) / float64(timeDeltaNs) * 1e9), nil } total, err := convertToRate(last.Cpu.Usage.Total, cur.Cpu.Usage.Total) if err != nil { return nil, err } percpu := make([]uint64, len(last.Cpu.Usage.PerCpu)) for i := range percpu { var err error percpu[i], err = convertToRate(last.Cpu.Usage.PerCpu[i], cur.Cpu.Usage.PerCpu[i]) if err != nil { return nil, err } } user, err := convertToRate(last.Cpu.Usage.User, cur.Cpu.Usage.User) if err != nil { return nil, err } system, err := convertToRate(last.Cpu.Usage.System, cur.Cpu.Usage.System) if err != nil { return nil, err } return &CpuInstStats{ Usage: CpuInstUsage{ Total: total, PerCpu: percpu, User: user, System: system, }, }, nil } // Get V2 container spec from v1 container info. func ContainerSpecFromV1(specV1 *v1.ContainerSpec, aliases []string, namespace string) ContainerSpec { specV2 := ContainerSpec{ CreationTime: specV1.CreationTime, HasCpu: specV1.HasCpu, HasMemory: specV1.HasMemory, HasFilesystem: specV1.HasFilesystem, HasNetwork: specV1.HasNetwork, HasDiskIo: specV1.HasDiskIo, HasCustomMetrics: specV1.HasCustomMetrics, Image: specV1.Image, Labels: specV1.Labels, Envs: specV1.Envs, } if specV1.HasCpu { specV2.Cpu.Limit = specV1.Cpu.Limit specV2.Cpu.MaxLimit = specV1.Cpu.MaxLimit specV2.Cpu.Mask = specV1.Cpu.Mask } if specV1.HasMemory { specV2.Memory.Limit = specV1.Memory.Limit specV2.Memory.Reservation = specV1.Memory.Reservation specV2.Memory.SwapLimit = specV1.Memory.SwapLimit } if specV1.HasCustomMetrics { specV2.CustomMetrics = specV1.CustomMetrics } specV2.Aliases = aliases specV2.Namespace = namespace return specV2 } cadvisor-0.27.1/info/v2/conversion_test.go000066400000000000000000000173461315410276000204620ustar00rootroot00000000000000// Copyright 2016 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 v2 import ( "reflect" "testing" "time" "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/assert" ) var ( timestamp = time.Date(1987, time.August, 10, 0, 0, 0, 0, time.UTC) labels = map[string]string{"foo": "bar"} envs = map[string]string{"foo": "bar"} ) func TestContanierSpecFromV1(t *testing.T) { v1Spec := v1.ContainerSpec{ CreationTime: timestamp, Labels: labels, Envs: envs, HasCpu: true, Cpu: v1.CpuSpec{ Limit: 2048, MaxLimit: 4096, Mask: "cpu_mask", }, HasMemory: true, Memory: v1.MemorySpec{ Limit: 2048, Reservation: 1024, SwapLimit: 8192, }, HasNetwork: true, HasFilesystem: true, HasDiskIo: true, HasCustomMetrics: true, CustomMetrics: []v1.MetricSpec{{ Name: "foo", Type: v1.MetricGauge, Format: v1.IntType, Units: "bars", }}, Image: "gcr.io/kubernetes/kubernetes:v1", } aliases := []string{"baz", "oof"} namespace := "foo_bar_baz" expectedV2Spec := ContainerSpec{ CreationTime: timestamp, Labels: labels, Envs: envs, HasCpu: true, Cpu: CpuSpec{ Limit: 2048, MaxLimit: 4096, Mask: "cpu_mask", }, HasMemory: true, Memory: MemorySpec{ Limit: 2048, Reservation: 1024, SwapLimit: 8192, }, HasNetwork: true, HasFilesystem: true, HasDiskIo: true, HasCustomMetrics: true, CustomMetrics: []v1.MetricSpec{{ Name: "foo", Type: v1.MetricGauge, Format: v1.IntType, Units: "bars", }}, Image: "gcr.io/kubernetes/kubernetes:v1", Aliases: aliases, Namespace: namespace, } v2Spec := ContainerSpecFromV1(&v1Spec, aliases, namespace) if !reflect.DeepEqual(v2Spec, expectedV2Spec) { t.Errorf("Converted spec differs from expectation!\nExpected: %+v\n Got: %+v\n", expectedV2Spec, v2Spec) } } func TestContainerStatsFromV1(t *testing.T) { v1Spec := v1.ContainerSpec{ CreationTime: timestamp, Labels: labels, HasCpu: true, Cpu: v1.CpuSpec{ Limit: 2048, MaxLimit: 4096, Mask: "cpu_mask", }, HasMemory: true, Memory: v1.MemorySpec{ Limit: 2048, Reservation: 1024, SwapLimit: 8192, }, HasNetwork: true, HasFilesystem: true, HasDiskIo: true, HasCustomMetrics: true, CustomMetrics: []v1.MetricSpec{{ Name: "foo", Type: v1.MetricGauge, Format: v1.IntType, Units: "bars", }}, Image: "gcr.io/kubernetes/kubernetes:v1", } v1Stats := v1.ContainerStats{ Timestamp: timestamp, Memory: v1.MemoryStats{ Usage: 1, Cache: 2, RSS: 3, WorkingSet: 4, Failcnt: 5, ContainerData: v1.MemoryStatsMemoryData{ Pgfault: 1, Pgmajfault: 2, }, HierarchicalData: v1.MemoryStatsMemoryData{ Pgfault: 10, Pgmajfault: 20, }, }, Network: v1.NetworkStats{ InterfaceStats: v1.InterfaceStats{ Name: "", RxBytes: 1, RxPackets: 2, RxErrors: 3, RxDropped: 4, TxBytes: 5, TxPackets: 6, TxErrors: 7, TxDropped: 8, }, Interfaces: []v1.InterfaceStats{{ Name: "eth0", RxBytes: 10, RxPackets: 20, RxErrors: 30, RxDropped: 40, TxBytes: 50, TxPackets: 60, TxErrors: 70, TxDropped: 80, }}, }, Filesystem: []v1.FsStats{{ Device: "dev0", Limit: 500, Usage: 100, BaseUsage: 50, Available: 300, InodesFree: 100, }}, } expectedV2Stats := ContainerStats{ Timestamp: timestamp, Cpu: &v1Stats.Cpu, DiskIo: &v1Stats.DiskIo, Memory: &v1Stats.Memory, Network: &NetworkStats{ Interfaces: v1Stats.Network.Interfaces, }, Filesystem: &FilesystemStats{ TotalUsageBytes: &v1Stats.Filesystem[0].Usage, BaseUsageBytes: &v1Stats.Filesystem[0].BaseUsage, InodeUsage: &v1Stats.Filesystem[0].Inodes, }, } v2Stats := ContainerStatsFromV1("test", &v1Spec, []*v1.ContainerStats{&v1Stats}) actualV2Stats := *v2Stats[0] if !reflect.DeepEqual(expectedV2Stats, actualV2Stats) { t.Errorf("Converted stats differs from expectation!\nExpected: %+v\n Got: %+v\n", expectedV2Stats, actualV2Stats) } } func TestInstCpuStats(t *testing.T) { tests := []struct { last *v1.ContainerStats cur *v1.ContainerStats want *CpuInstStats }{ // Last is missing { nil, &v1.ContainerStats{}, nil, }, // Goes back in time { &v1.ContainerStats{ Timestamp: time.Unix(100, 0).Add(time.Second), }, &v1.ContainerStats{ Timestamp: time.Unix(100, 0), }, nil, }, // Zero time delta { &v1.ContainerStats{ Timestamp: time.Unix(100, 0), }, &v1.ContainerStats{ Timestamp: time.Unix(100, 0), }, nil, }, // Unexpectedly small time delta { &v1.ContainerStats{ Timestamp: time.Unix(100, 0), }, &v1.ContainerStats{ Timestamp: time.Unix(100, 0).Add(30 * time.Millisecond), }, nil, }, // Different number of cpus { &v1.ContainerStats{ Timestamp: time.Unix(100, 0), Cpu: v1.CpuStats{ Usage: v1.CpuUsage{ PerCpu: []uint64{100, 200}, }, }, }, &v1.ContainerStats{ Timestamp: time.Unix(100, 0).Add(time.Second), Cpu: v1.CpuStats{ Usage: v1.CpuUsage{ PerCpu: []uint64{100, 200, 300}, }, }, }, nil, }, // Stat numbers decrease { &v1.ContainerStats{ Timestamp: time.Unix(100, 0), Cpu: v1.CpuStats{ Usage: v1.CpuUsage{ Total: 300, PerCpu: []uint64{100, 200}, User: 250, System: 50, }, }, }, &v1.ContainerStats{ Timestamp: time.Unix(100, 0).Add(time.Second), Cpu: v1.CpuStats{ Usage: v1.CpuUsage{ Total: 200, PerCpu: []uint64{100, 100}, User: 150, System: 50, }, }, }, nil, }, // One second elapsed { &v1.ContainerStats{ Timestamp: time.Unix(100, 0), Cpu: v1.CpuStats{ Usage: v1.CpuUsage{ Total: 300, PerCpu: []uint64{100, 200}, User: 250, System: 50, }, }, }, &v1.ContainerStats{ Timestamp: time.Unix(100, 0).Add(time.Second), Cpu: v1.CpuStats{ Usage: v1.CpuUsage{ Total: 500, PerCpu: []uint64{200, 300}, User: 400, System: 100, }, }, }, &CpuInstStats{ Usage: CpuInstUsage{ Total: 200, PerCpu: []uint64{100, 100}, User: 150, System: 50, }, }, }, // Two seconds elapsed { &v1.ContainerStats{ Timestamp: time.Unix(100, 0), Cpu: v1.CpuStats{ Usage: v1.CpuUsage{ Total: 300, PerCpu: []uint64{100, 200}, User: 250, System: 50, }, }, }, &v1.ContainerStats{ Timestamp: time.Unix(100, 0).Add(2 * time.Second), Cpu: v1.CpuStats{ Usage: v1.CpuUsage{ Total: 500, PerCpu: []uint64{200, 300}, User: 400, System: 100, }, }, }, &CpuInstStats{ Usage: CpuInstUsage{ Total: 100, PerCpu: []uint64{50, 50}, User: 75, System: 25, }, }, }, } for _, c := range tests { got, err := InstCpuStats(c.last, c.cur) if err != nil { if c.want == nil { continue } t.Errorf("Unexpected error: %v", err) } assert.Equal(t, c.want, got) } } cadvisor-0.27.1/info/v2/machine.go000066400000000000000000000152631315410276000166360ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v2 import ( // TODO(rjnagal): Move structs from v1. "time" "github.com/google/cadvisor/info/v1" ) type Attributes 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"` // Docker API version. DockerAPIVersion string `json:"docker_api_version"` // cAdvisor version. CadvisorVersion string `json:"cadvisor_version"` // The number of cores in this machine. NumCores int `json:"num_cores"` // Maximum clock speed for the cores, in KHz. CpuFrequency uint64 `json:"cpu_frequency_khz"` // The amount of memory (in bytes) in this machine MemoryCapacity uint64 `json:"memory_capacity"` // The machine id MachineID string `json:"machine_id"` // The system uuid SystemUUID string `json:"system_uuid"` // HugePages on this machine. HugePages []v1.HugePagesInfo `json:"hugepages"` // Filesystems on this machine. Filesystems []v1.FsInfo `json:"filesystems"` // Disk map DiskMap map[string]v1.DiskInfo `json:"disk_map"` // Network devices NetworkDevices []v1.NetInfo `json:"network_devices"` // Machine Topology // Describes cpu/memory layout and hierarchy. Topology []v1.Node `json:"topology"` // Cloud provider the machine belongs to CloudProvider v1.CloudProvider `json:"cloud_provider"` // Type of cloud instance (e.g. GCE standard) the machine is. InstanceType v1.InstanceType `json:"instance_type"` } func GetAttributes(mi *v1.MachineInfo, vi *v1.VersionInfo) Attributes { return Attributes{ KernelVersion: vi.KernelVersion, ContainerOsVersion: vi.ContainerOsVersion, DockerVersion: vi.DockerVersion, DockerAPIVersion: vi.DockerAPIVersion, CadvisorVersion: vi.CadvisorVersion, NumCores: mi.NumCores, CpuFrequency: mi.CpuFrequency, MemoryCapacity: mi.MemoryCapacity, MachineID: mi.MachineID, SystemUUID: mi.SystemUUID, Filesystems: mi.Filesystems, DiskMap: mi.DiskMap, NetworkDevices: mi.NetworkDevices, Topology: mi.Topology, CloudProvider: mi.CloudProvider, InstanceType: mi.InstanceType, } } // MachineStats contains usage statistics for the entire machine. type MachineStats struct { // The time of this stat point. Timestamp time.Time `json:"timestamp"` // In nanoseconds (aggregated) Cpu *v1.CpuStats `json:"cpu,omitempty"` // In nanocores per second (instantaneous) CpuInst *CpuInstStats `json:"cpu_inst,omitempty"` // Memory statistics Memory *v1.MemoryStats `json:"memory,omitempty"` // Network statistics Network *NetworkStats `json:"network,omitempty"` // Filesystem statistics Filesystem []MachineFsStats `json:"filesystem,omitempty"` // Task load statistics Load *v1.LoadStats `json:"load_stats,omitempty"` } // MachineFsStats contains per filesystem capacity and usage information. type MachineFsStats struct { // The block device name associated with the filesystem. Device string `json:"device"` // Type of filesystem. Type string `json:"type"` // Number of bytes that can be consumed on this filesystem. Capacity *uint64 `json:"capacity,omitempty"` // Number of bytes that is currently consumed on this filesystem. Usage *uint64 `json:"usage,omitempty"` // Number of bytes available for non-root user on this filesystem. Available *uint64 `json:"available,omitempty"` // Number of inodes that are available on this filesystem. InodesFree *uint64 `json:"inodes_free,omitempty"` // DiskStats for this device. DiskStats `json:"inline"` } // DiskStats contains per partition usage information. // This information is only available at the machine level. type DiskStats struct { // Number of reads completed // This is the total number of reads completed successfully. ReadsCompleted *uint64 `json:"reads_completed,omitempty"` // Number of reads merged // Reads and writes which are adjacent to each other may be merged for // efficiency. Thus two 4K reads may become one 8K read before it is // ultimately handed to the disk, and so it will be counted (and queued) // as only one I/O. This field lets you know how often this was done. ReadsMerged *uint64 `json:"reads_merged,omitempty"` // Number of sectors read // This is the total number of sectors read successfully. SectorsRead *uint64 `json:"sectors_read,omitempty"` // Time spent reading // This is the total number of milliseconds spent by all reads (as // measured from __make_request() to end_that_request_last()). ReadDuration *time.Duration `json:"read_duration,omitempty"` // Number of writes completed // This is the total number of writes completed successfully. WritesCompleted *uint64 `json:"writes_completed,omitempty"` // Number of writes merged // See the description of reads merged. WritesMerged *uint64 `json:"writes_merged,omitempty"` // Number of sectors written // This is the total number of sectors written successfully. SectorsWritten *uint64 `json:"sectors_written,omitempty"` // Time spent writing // This is the total number of milliseconds spent by all writes (as // measured from __make_request() to end_that_request_last()). WriteDuration *time.Duration `json:"write_duration,omitempty"` // Number of I/Os currently in progress // The only field that should go to zero. Incremented as requests are // given to appropriate struct request_queue and decremented as they finish. IoInProgress *uint64 `json:"io_in_progress,omitempty"` // Time spent doing I/Os // This field increases so long as field 9 is nonzero. IoDuration *time.Duration `json:"io_duration,omitempty"` // weighted time spent doing I/Os // This field is incremented at each I/O start, I/O completion, I/O // merge, or read of these stats by the number of I/Os in progress // (field 9) times the number of milliseconds spent doing I/O since the // last update of this field. This can provide an easy measure of both // I/O completion time and the backlog that may be accumulating. WeightedIoDuration *time.Duration `json:"weighted_io_duration,omitempty"` } cadvisor-0.27.1/integration/000077500000000000000000000000001315410276000157355ustar00rootroot00000000000000cadvisor-0.27.1/integration/framework/000077500000000000000000000000001315410276000177325ustar00rootroot00000000000000cadvisor-0.27.1/integration/framework/framework.go000066400000000000000000000244111315410276000222600ustar00rootroot00000000000000// 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 framework import ( "bytes" "flag" "fmt" "os/exec" "strings" "testing" "time" "github.com/golang/glog" "github.com/google/cadvisor/client" "github.com/google/cadvisor/client/v2" ) var host = flag.String("host", "localhost", "Address of the host being tested") var port = flag.Int("port", 8080, "Port of the application on the host being tested") var sshOptions = flag.String("ssh-options", "", "Command line options for ssh") // Integration test framework. type Framework interface { // Clean the framework state. Cleanup() // The testing.T used by the framework and the current test. T() *testing.T // Returns the hostname being tested. Hostname() HostnameInfo // Returns the Docker actions for the test framework. Docker() DockerActions // Returns the shell actions for the test framework. Shell() ShellActions // Returns the cAdvisor actions for the test framework. Cadvisor() CadvisorActions } // Instantiates a Framework. Cleanup *must* be called. Class is thread-compatible. // All framework actions report fatal errors on the t specified at creation time. // // Typical use: // // func TestFoo(t *testing.T) { // fm := framework.New(t) // defer fm.Cleanup() // ... actual test ... // } func New(t *testing.T) Framework { // All integration tests are large. if testing.Short() { t.Skip("Skipping framework test in short mode") } // Try to see if non-localhost hosts are GCE instances. fm := &realFramework{ hostname: HostnameInfo{ Host: *host, Port: *port, }, t: t, cleanups: make([]func(), 0), } fm.shellActions = shellActions{ fm: fm, } fm.dockerActions = dockerActions{ fm: fm, } return fm } const ( Aufs string = "aufs" Overlay string = "overlay" Overlay2 string = "overlay2" DeviceMapper string = "devicemapper" Unknown string = "" ) type DockerActions interface { // Run the no-op pause Docker container and return its ID. RunPause() string // Run the specified command in a Docker busybox container and return its ID. RunBusybox(cmd ...string) string // Runs a Docker container in the background. Uses the specified DockerRunArgs and command. // Returns the ID of the new container. // // e.g.: // Run(DockerRunArgs{Image: "busybox"}, "ping", "www.google.com") // -> docker run busybox ping www.google.com Run(args DockerRunArgs, cmd ...string) string RunStress(args DockerRunArgs, cmd ...string) string Version() []string StorageDriver() string } type ShellActions interface { // Runs a specified command and arguments. Returns the stdout and stderr. Run(cmd string, args ...string) (string, string) RunStress(cmd string, args ...string) (string, string) } type CadvisorActions interface { // Returns a cAdvisor client to the machine being tested. Client() *client.Client ClientV2() *v2.Client } type realFramework struct { hostname HostnameInfo t *testing.T cadvisorClient *client.Client cadvisorClientV2 *v2.Client shellActions shellActions dockerActions dockerActions // Cleanup functions to call on Cleanup() cleanups []func() } type shellActions struct { fm *realFramework } type dockerActions struct { fm *realFramework } type HostnameInfo struct { Host string Port int } // Returns: http://:/ func (self HostnameInfo) FullHostname() string { return fmt.Sprintf("http://%s:%d/", self.Host, self.Port) } func (self *realFramework) T() *testing.T { return self.t } func (self *realFramework) Hostname() HostnameInfo { return self.hostname } func (self *realFramework) Shell() ShellActions { return self.shellActions } func (self *realFramework) Docker() DockerActions { return self.dockerActions } func (self *realFramework) Cadvisor() CadvisorActions { return self } // Call all cleanup functions. func (self *realFramework) Cleanup() { for _, cleanupFunc := range self.cleanups { cleanupFunc() } } // Gets a client to the cAdvisor being tested. func (self *realFramework) Client() *client.Client { if self.cadvisorClient == nil { cadvisorClient, err := client.NewClient(self.Hostname().FullHostname()) if err != nil { self.t.Fatalf("Failed to instantiate the cAdvisor client: %v", err) } self.cadvisorClient = cadvisorClient } return self.cadvisorClient } // Gets a v2 client to the cAdvisor being tested. func (self *realFramework) ClientV2() *v2.Client { if self.cadvisorClientV2 == nil { cadvisorClientV2, err := v2.NewClient(self.Hostname().FullHostname()) if err != nil { self.t.Fatalf("Failed to instantiate the cAdvisor client: %v", err) } self.cadvisorClientV2 = cadvisorClientV2 } return self.cadvisorClientV2 } func (self dockerActions) RunPause() string { return self.Run(DockerRunArgs{ Image: "kubernetes/pause", }) } // Run the specified command in a Docker busybox container. func (self dockerActions) RunBusybox(cmd ...string) string { return self.Run(DockerRunArgs{ Image: "busybox", }, cmd...) } type DockerRunArgs struct { // Image to use. Image string // Arguments to the Docker CLI. Args []string InnerArgs []string } // TODO(vmarmol): Use the Docker remote API. // TODO(vmarmol): Refactor a set of "RunCommand" actions. // Runs a Docker container in the background. Uses the specified DockerRunArgs and command. // // e.g.: // RunDockerContainer(DockerRunArgs{Image: "busybox"}, "ping", "www.google.com") // -> docker run busybox ping www.google.com func (self dockerActions) Run(args DockerRunArgs, cmd ...string) string { dockerCommand := append(append([]string{"docker", "run", "-d"}, args.Args...), args.Image) dockerCommand = append(dockerCommand, cmd...) output, _ := self.fm.Shell().Run("sudo", dockerCommand...) // The last line is the container ID. elements := strings.Fields(output) containerId := elements[len(elements)-1] self.fm.cleanups = append(self.fm.cleanups, func() { self.fm.Shell().Run("sudo", "docker", "rm", "-f", containerId) }) return containerId } func (self dockerActions) Version() []string { dockerCommand := []string{"docker", "version", "-f", "'{{.Server.Version}}'"} output, _ := self.fm.Shell().Run("sudo", dockerCommand...) output = strings.TrimSpace(output) ret := strings.Split(output, ".") if len(ret) != 3 { self.fm.T().Fatalf("invalid version %v", output) } return ret } func (self dockerActions) StorageDriver() string { dockerCommand := []string{"docker", "info"} output, _ := self.fm.Shell().Run("sudo", dockerCommand...) if len(output) < 1 { self.fm.T().Fatalf("failed to find docker storage driver - %v", output) } for _, line := range strings.Split(output, "\n") { line = strings.TrimSpace(line) if strings.HasPrefix(line, "Storage Driver: ") { idx := strings.LastIndex(line, ": ") + 2 driver := line[idx:] switch driver { case Aufs, Overlay, Overlay2, DeviceMapper: return driver default: return Unknown } } } self.fm.T().Fatalf("failed to find docker storage driver from info - %v", output) return Unknown } func (self dockerActions) RunStress(args DockerRunArgs, cmd ...string) string { dockerCommand := append(append(append(append([]string{"docker", "run", "-m=4M", "-d", "-t", "-i"}, args.Args...), args.Image), args.InnerArgs...), cmd...) output, _ := self.fm.Shell().RunStress("sudo", dockerCommand...) // The last line is the container ID. if len(output) < 1 { self.fm.T().Fatalf("need 1 arguments in output %v to get the name but have %v", output, len(output)) } elements := strings.Fields(output) containerId := elements[len(elements)-1] self.fm.cleanups = append(self.fm.cleanups, func() { self.fm.Shell().Run("sudo", "docker", "rm", "-f", containerId) }) return containerId } func (self shellActions) wrapSsh(command string, args ...string) *exec.Cmd { cmd := []string{self.fm.Hostname().Host, "--", "sh", "-c", "\"", command} cmd = append(cmd, args...) cmd = append(cmd, "\"") if *sshOptions != "" { cmd = append(strings.Split(*sshOptions, " "), cmd...) } return exec.Command("ssh", cmd...) } func (self shellActions) Run(command string, args ...string) (string, string) { var cmd *exec.Cmd if self.fm.Hostname().Host == "localhost" { // Just run locally. cmd = exec.Command(command, args...) } else { // We must SSH to the remote machine and run the command. cmd = self.wrapSsh(command, args...) } var stdout bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr glog.Infof("About to run - %v", cmd.Args) err := cmd.Run() if err != nil { self.fm.T().Fatalf("Failed to run %q %v in %q with error: %q. Stdout: %q, Stderr: %s", command, args, self.fm.Hostname().Host, err, stdout.String(), stderr.String()) return "", "" } return stdout.String(), stderr.String() } func (self shellActions) RunStress(command string, args ...string) (string, string) { var cmd *exec.Cmd if self.fm.Hostname().Host == "localhost" { // Just run locally. cmd = exec.Command(command, args...) } else { // We must SSH to the remote machine and run the command. cmd = self.wrapSsh(command, args...) } var stdout bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { self.fm.T().Logf("Ran %q %v in %q and received error: %q. Stdout: %q, Stderr: %s", command, args, self.fm.Hostname().Host, err, stdout.String(), stderr.String()) return stdout.String(), stderr.String() } return stdout.String(), stderr.String() } // Runs retryFunc until no error is returned. After dur time the last error is returned. // Note that the function does not timeout the execution of retryFunc when the limit is reached. func RetryForDuration(retryFunc func() error, dur time.Duration) error { waitUntil := time.Now().Add(dur) var err error for time.Now().Before(waitUntil) { err = retryFunc() if err == nil { return nil } } return err } cadvisor-0.27.1/integration/runner/000077500000000000000000000000001315410276000172465ustar00rootroot00000000000000cadvisor-0.27.1/integration/runner/retrywhitelist.txt000066400000000000000000000001311315410276000231040ustar00rootroot00000000000000Network tx and rx bytes should not be equal Network tx and rx packets should not be equalcadvisor-0.27.1/integration/runner/run.sh000077500000000000000000000020631315410276000204120ustar00rootroot00000000000000#!/bin/bash # Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -e set -x # Check usage. if [ $# == 0 ]; then echo "USAGE: run.sh " exit 1 fi # Don't run on trivial changes. if ! git diff --name-only origin/master | grep -c -E "*.go|*.sh" &> /dev/null; then echo "This PR does not touch files that require integration testing. Skipping integration tests." exit 0 fi # Build the runner. go build github.com/google/cadvisor/integration/runner # Run it. HOSTS=$@ ./runner --logtostderr $HOSTS cadvisor-0.27.1/integration/runner/runner.go000066400000000000000000000205211315410276000211060ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bufio" "bytes" "encoding/json" "errors" "flag" "fmt" "io/ioutil" "net/http" "os" "os/exec" "path" "regexp" "strconv" "strings" "sync" "time" "github.com/golang/glog" cadvisorApi "github.com/google/cadvisor/info/v2" ) // must be able to ssh into hosts without password // go run ./integration/runner/runner.go --logtostderr --v 2 --ssh-config <.ssh/config file> const ( cadvisorBinary = "cadvisor" testTimeout = 15 * time.Minute ) var cadvisorTimeout = flag.Duration("cadvisor_timeout", 15*time.Second, "Time to wait for cAdvisor to come up on the remote host") var port = flag.Int("port", 8080, "Port in which to start cAdvisor in the remote host") var testRetryCount = flag.Int("test-retry-count", 3, "Number of times to retry failed tests before failing.") var testRetryWhitelist = flag.String("test-retry-whitelist", "", "Path to newline separated list of regexexp for test failures that should be retried. If empty, no tests are retried.") var sshOptions = flag.String("ssh-options", "", "Commandline options passed to ssh.") var retryRegex *regexp.Regexp func getAttributes(ipAddress, portStr string) (*cadvisorApi.Attributes, error) { // Get host attributes and log attributes if the tests fail. var attributes cadvisorApi.Attributes resp, err := http.Get(fmt.Sprintf("http://%s:%s/api/v2.1/attributes", ipAddress, portStr)) if err != nil { return nil, fmt.Errorf("failed to get attributes - %v", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to get attributes. Status code - %v", resp.StatusCode) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("unable to read attributes response body - %v", err) } if err := json.Unmarshal(body, &attributes); err != nil { return nil, fmt.Errorf("failed to unmarshal attributes - %v", err) } return &attributes, nil } func RunCommand(cmd string, args ...string) error { output, err := exec.Command(cmd, args...).CombinedOutput() if err != nil { return fmt.Errorf("command %q %q failed with error: %v and output: %s", cmd, args, err, output) } return nil } func RunSshCommand(cmd string, args ...string) error { if *sshOptions != "" { args = append(strings.Split(*sshOptions, " "), args...) } return RunCommand(cmd, args...) } func PushAndRunTests(host, testDir string) (result error) { // Push binary. glog.Infof("Pushing cAdvisor binary to %q...", host) err := RunSshCommand("ssh", host, "--", "mkdir", "-p", testDir) if err != nil { return fmt.Errorf("failed to make remote testing directory: %v", err) } defer func() { err = RunSshCommand("ssh", host, "--", "rm", "-rf", testDir) if err != nil { glog.Errorf("Failed to cleanup test directory: %v", err) } }() err = RunSshCommand("scp", "-r", cadvisorBinary, fmt.Sprintf("%s:%s", host, testDir)) if err != nil { return fmt.Errorf("failed to copy binary: %v", err) } // Start cAdvisor. glog.Infof("Running cAdvisor on %q...", host) portStr := strconv.Itoa(*port) errChan := make(chan error) go func() { err = RunSshCommand("ssh", host, "--", fmt.Sprintf("sudo GORACE='halt_on_error=1' %s --port %s --logtostderr --docker_env_metadata_whitelist=TEST_VAR &> %s/log.txt", path.Join(testDir, cadvisorBinary), portStr, testDir)) if err != nil { errChan <- fmt.Errorf("error running cAdvisor: %v", err) } }() defer func() { err = RunSshCommand("ssh", host, "--", "sudo", "pkill", cadvisorBinary) if err != nil { glog.Errorf("Failed to cleanup: %v", err) } }() defer func() { if result != nil { // Copy logs from the host err := RunSshCommand("scp", fmt.Sprintf("%s:%s/log.txt", host, testDir), "./") if err != nil { result = fmt.Errorf("error fetching logs: %v for %v", err, result) return } defer os.Remove("./log.txt") logs, err := ioutil.ReadFile("./log.txt") if err != nil { result = fmt.Errorf("error reading local log file: %v for %v", err, result) return } glog.Errorf("----------------------\nLogs from Host: %q\n%v\n", host, string(logs)) // Get attributes for debugging purposes. attributes, err := getAttributes(host, portStr) if err != nil { glog.Errorf("Failed to read host attributes: %v", err) } result = fmt.Errorf("error on host %s: %v\n%+v", host, result, attributes) } }() // Wait for cAdvisor to come up. endTime := time.Now().Add(*cadvisorTimeout) done := false for endTime.After(time.Now()) && !done { select { case err := <-errChan: // Quit early if there was an error. return err case <-time.After(500 * time.Millisecond): // Stop waiting when cAdvisor is healthy.. resp, err := http.Get(fmt.Sprintf("http://%s:%s/healthz", host, portStr)) if err == nil && resp.StatusCode == http.StatusOK { done = true break } } } if !done { return fmt.Errorf("timed out waiting for cAdvisor to come up at host %q", host) } // Run the tests in a retry loop. glog.Infof("Running integration tests targeting %q...", host) for i := 0; i <= *testRetryCount; i++ { // Check if this is a retry if i > 0 { time.Sleep(time.Second * 15) // Wait 15 seconds before retrying glog.Warningf("Retrying (%d of %d) tests on host %s due to error %v", i, *testRetryCount, host, err) } // Run the command err = RunCommand("go", "test", "--timeout", testTimeout.String(), "github.com/google/cadvisor/integration/tests/...", "--host", host, "--port", portStr, "--ssh-options", *sshOptions) if err == nil { // On success, break out of retry loop break } // Only retry on test failures caused by these known flaky failure conditions if retryRegex == nil || !retryRegex.Match([]byte(err.Error())) { glog.Warningf("Skipping retry for tests on host %s because error is not whitelisted", host) break } } return err } func Run() error { start := time.Now() defer func() { glog.Infof("Execution time %v", time.Since(start)) }() defer glog.Flush() hosts := flag.Args() testDir := fmt.Sprintf("/tmp/cadvisor-%d", os.Getpid()) glog.Infof("Running integration tests on host(s) %q", strings.Join(hosts, ",")) // Build cAdvisor. glog.Infof("Building cAdvisor...") err := RunCommand("build/build.sh") if err != nil { return err } defer func() { err := RunCommand("rm", cadvisorBinary) if err != nil { glog.Error(err) } }() // Run test on all hosts in parallel. var wg sync.WaitGroup allErrors := make([]error, 0) var allErrorsLock sync.Mutex for _, host := range hosts { wg.Add(1) go func(host string) { defer wg.Done() err := PushAndRunTests(host, testDir) if err != nil { func() { allErrorsLock.Lock() defer allErrorsLock.Unlock() allErrors = append(allErrors, err) }() } }(host) } wg.Wait() if len(allErrors) != 0 { var buffer bytes.Buffer for i, err := range allErrors { buffer.WriteString(fmt.Sprintf("Error %d: ", i)) buffer.WriteString(err.Error()) buffer.WriteString("\n") } return errors.New(buffer.String()) } glog.Infof("All tests pass!") return nil } // initRetryWhitelist initializes the whitelist of test failures that can be retried. func initRetryWhitelist() { if *testRetryWhitelist == "" { return } file, err := os.Open(*testRetryWhitelist) if err != nil { glog.Fatal(err) } defer file.Close() retryStrings := []string{} scanner := bufio.NewScanner(file) for scanner.Scan() { text := scanner.Text() if text != "" { retryStrings = append(retryStrings, text) } } if err := scanner.Err(); err != nil { glog.Fatal(err) } retryRegex = regexp.MustCompile(strings.Join(retryStrings, "|")) } func main() { flag.Parse() // Check usage. if len(flag.Args()) == 0 { glog.Fatalf("USAGE: runner ") } initRetryWhitelist() // Run the tests. err := Run() if err != nil { glog.Fatal(err) } } cadvisor-0.27.1/integration/tests/000077500000000000000000000000001315410276000170775ustar00rootroot00000000000000cadvisor-0.27.1/integration/tests/TODO.md000066400000000000000000000001711315410276000201650ustar00rootroot00000000000000Tests to Write: - UI comes up -- / -> /containers -- /containers -- /docker - API tests -- /containers -- /subcontainers cadvisor-0.27.1/integration/tests/api/000077500000000000000000000000001315410276000176505ustar00rootroot00000000000000cadvisor-0.27.1/integration/tests/api/attributes_test.go000066400000000000000000000020711315410276000234240ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package api import ( "testing" "github.com/google/cadvisor/integration/framework" "github.com/stretchr/testify/assert" ) func TestAttributeInformationIsReturned(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() attributes, err := fm.Cadvisor().ClientV2().Attributes() if err != nil { t.Fatal(err) } vp := `\d+\.\d+\.\d+` assert.True(t, assert.Regexp(t, vp, attributes.DockerVersion), "Expected %s to match %s", attributes.DockerVersion, vp) } cadvisor-0.27.1/integration/tests/api/docker_test.go000066400000000000000000000305741315410276000225160ustar00rootroot00000000000000// 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 api import ( "fmt" "os" "strconv" "testing" "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/integration/framework" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Sanity check the container by: // - Checking that the specified alias is a valid one for this container. // - Verifying that stats are not empty. func sanityCheck(alias string, containerInfo info.ContainerInfo, t *testing.T) { assert.Contains(t, containerInfo.Aliases, alias, "Alias %q should be in list of aliases %v", alias, containerInfo.Aliases) assert.NotEmpty(t, containerInfo.Stats, "Expected container to have stats") } // Sanity check the container by: // - Checking that the specified alias is a valid one for this container. // - Verifying that stats are not empty. func sanityCheckV2(alias string, info v2.ContainerInfo, t *testing.T) { assert.Contains(t, info.Spec.Aliases, alias, "Alias %q should be in list of aliases %v", alias, info.Spec.Aliases) assert.NotEmpty(t, info.Stats, "Expected container to have stats") } // Waits up to 5s for a container with the specified alias to appear. func waitForContainer(alias string, fm framework.Framework) { err := framework.RetryForDuration(func() error { ret, err := fm.Cadvisor().Client().DockerContainer(alias, &info.ContainerInfoRequest{ NumStats: 1, }) if err != nil { return err } if len(ret.Stats) != 1 { return fmt.Errorf("no stats returned for container %q", alias) } return nil }, 5*time.Second) require.NoError(fm.T(), err, "Timed out waiting for container %q to be available in cAdvisor: %v", alias, err) } func getDockerMinorVersion(fm framework.Framework) int { val, err := strconv.Atoi(fm.Docker().Version()[1]) assert.Nil(fm.T(), err) return val } // A Docker container in /docker/ func TestDockerContainerById(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() containerId := fm.Docker().RunPause() // Wait for the container to show up. waitForContainer(containerId, fm) request := &info.ContainerInfoRequest{ NumStats: 1, } containerInfo, err := fm.Cadvisor().Client().DockerContainer(containerId, request) require.NoError(t, err) sanityCheck(containerId, containerInfo, t) } // A Docker container in /docker/ func TestDockerContainerByName(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() containerName := fmt.Sprintf("test-docker-container-by-name-%d", os.Getpid()) fm.Docker().Run(framework.DockerRunArgs{ Image: "kubernetes/pause", Args: []string{"--name", containerName}, }) // Wait for the container to show up. waitForContainer(containerName, fm) request := &info.ContainerInfoRequest{ NumStats: 1, } containerInfo, err := fm.Cadvisor().Client().DockerContainer(containerName, request) require.NoError(t, err) sanityCheck(containerName, containerInfo, t) } // Find the first container with the specified alias in containers. func findContainer(alias string, containers []info.ContainerInfo, t *testing.T) info.ContainerInfo { for _, cont := range containers { for _, a := range cont.Aliases { if alias == a { return cont } } } t.Fatalf("Failed to find container %q in %+v", alias, containers) return info.ContainerInfo{} } // All Docker containers through /docker func TestGetAllDockerContainers(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() // Wait for the containers to show up. containerId1 := fm.Docker().RunPause() containerId2 := fm.Docker().RunPause() waitForContainer(containerId1, fm) waitForContainer(containerId2, fm) request := &info.ContainerInfoRequest{ NumStats: 1, } containersInfo, err := fm.Cadvisor().Client().AllDockerContainers(request) require.NoError(t, err) if len(containersInfo) < 2 { t.Fatalf("At least 2 Docker containers should exist, received %d: %+v", len(containersInfo), containersInfo) } sanityCheck(containerId1, findContainer(containerId1, containersInfo, t), t) sanityCheck(containerId2, findContainer(containerId2, containersInfo, t), t) } // Check expected properties of a Docker container. func TestBasicDockerContainer(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() containerName := fmt.Sprintf("test-basic-docker-container-%d", os.Getpid()) containerId := fm.Docker().Run(framework.DockerRunArgs{ Image: "kubernetes/pause", Args: []string{ "--name", containerName, }, }) // Wait for the container to show up. waitForContainer(containerId, fm) request := &info.ContainerInfoRequest{ NumStats: 1, } containerInfo, err := fm.Cadvisor().Client().DockerContainer(containerId, request) require.NoError(t, err) // Check that the contianer is known by both its name and ID. sanityCheck(containerId, containerInfo, t) sanityCheck(containerName, containerInfo, t) assert.Empty(t, containerInfo.Subcontainers, "Should not have subcontainers") assert.Len(t, containerInfo.Stats, 1, "Should have exactly one stat") } // TODO(vmarmol): Handle if CPU or memory is not isolated on this system. // Check the ContainerSpec. func TestDockerContainerSpec(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() var ( cpuShares = uint64(2048) cpuMask = "0" memoryLimit = uint64(1 << 30) // 1GB image = "kubernetes/pause" env = map[string]string{"test_var": "FOO"} labels = map[string]string{"bar": "baz"} ) cpusetArg := "--cpuset" if getDockerMinorVersion(fm) >= 10 { cpusetArg = "--cpuset-cpus" } containerId := fm.Docker().Run(framework.DockerRunArgs{ Image: image, Args: []string{ "--cpu-shares", strconv.FormatUint(cpuShares, 10), cpusetArg, cpuMask, "--memory", strconv.FormatUint(memoryLimit, 10), "--env", "TEST_VAR=FOO", "--label", "bar=baz", }, }) // Wait for the container to show up. waitForContainer(containerId, fm) request := &info.ContainerInfoRequest{ NumStats: 1, } containerInfo, err := fm.Cadvisor().Client().DockerContainer(containerId, request) require.NoError(t, err) sanityCheck(containerId, containerInfo, t) assert := assert.New(t) assert.True(containerInfo.Spec.HasCpu, "CPU should be isolated") assert.Equal(cpuShares, containerInfo.Spec.Cpu.Limit, "Container should have %d shares, has %d", cpuShares, containerInfo.Spec.Cpu.Limit) assert.Equal(cpuMask, containerInfo.Spec.Cpu.Mask, "Cpu mask should be %q, but is %q", cpuMask, containerInfo.Spec.Cpu.Mask) assert.True(containerInfo.Spec.HasMemory, "Memory should be isolated") assert.Equal(memoryLimit, containerInfo.Spec.Memory.Limit, "Container should have memory limit of %d, has %d", memoryLimit, containerInfo.Spec.Memory.Limit) assert.True(containerInfo.Spec.HasNetwork, "Network should be isolated") assert.True(containerInfo.Spec.HasDiskIo, "Blkio should be isolated") assert.Equal(image, containerInfo.Spec.Image, "Spec should include container image") assert.Equal(env, containerInfo.Spec.Envs, "Spec should include environment variables") assert.Equal(labels, containerInfo.Spec.Labels, "Spec should include labels") } // Check the CPU ContainerStats. func TestDockerContainerCpuStats(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() // Wait for the container to show up. containerId := fm.Docker().RunBusybox("ping", "www.google.com") waitForContainer(containerId, fm) request := &info.ContainerInfoRequest{ NumStats: 1, } containerInfo, err := fm.Cadvisor().Client().DockerContainer(containerId, request) if err != nil { t.Fatal(err) } sanityCheck(containerId, containerInfo, t) // Checks for CpuStats. checkCpuStats(t, containerInfo.Stats[0].Cpu) } // Check the memory ContainerStats. func TestDockerContainerMemoryStats(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() // Wait for the container to show up. containerId := fm.Docker().RunBusybox("ping", "www.google.com") waitForContainer(containerId, fm) request := &info.ContainerInfoRequest{ NumStats: 1, } containerInfo, err := fm.Cadvisor().Client().DockerContainer(containerId, request) require.NoError(t, err) sanityCheck(containerId, containerInfo, t) // Checks for MemoryStats. checkMemoryStats(t, containerInfo.Stats[0].Memory) } // Check the network ContainerStats. func TestDockerContainerNetworkStats(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() // Wait for the container to show up. containerId := fm.Docker().RunBusybox("watch", "-n1", "wget", "http://www.google.com/") waitForContainer(containerId, fm) time.Sleep(10 * time.Second) request := &info.ContainerInfoRequest{ NumStats: 1, } containerInfo, err := fm.Cadvisor().Client().DockerContainer(containerId, request) require.NoError(t, err) sanityCheck(containerId, containerInfo, t) // Checks for NetworkStats. stat := containerInfo.Stats[0] assert := assert.New(t) assert.NotEqual(0, stat.Network.TxBytes, "Network tx bytes should not be zero") assert.NotEqual(0, stat.Network.TxPackets, "Network tx packets should not be zero") assert.NotEqual(0, stat.Network.RxBytes, "Network rx bytes should not be zero") assert.NotEqual(0, stat.Network.RxPackets, "Network rx packets should not be zero") assert.NotEqual(stat.Network.RxBytes, stat.Network.TxBytes, "Network tx and rx bytes should not be equal") assert.NotEqual(stat.Network.RxPackets, stat.Network.TxPackets, "Network tx and rx packets should not be equal") } func TestDockerFilesystemStats(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() storageDriver := fm.Docker().StorageDriver() if storageDriver == framework.DeviceMapper { // Filesystem stats not supported with devicemapper, yet return } const ( ddUsage = uint64(1 << 3) // 1 KB sleepDuration = 10 * time.Second ) // Wait for the container to show up. // FIXME: Tests should be bundled and run on the remote host instead of being run over ssh. // Escaping bash over ssh is ugly. // Once github issue 1130 is fixed, this logic can be removed. dockerCmd := fmt.Sprintf("dd if=/dev/zero of=/file count=2 bs=%d & ping google.com", ddUsage) if fm.Hostname().Host != "localhost" { dockerCmd = fmt.Sprintf("'%s'", dockerCmd) } containerId := fm.Docker().RunBusybox("/bin/sh", "-c", dockerCmd) waitForContainer(containerId, fm) request := &v2.RequestOptions{ IdType: v2.TypeDocker, Count: 1, } needsBaseUsageCheck := false switch storageDriver { case framework.Aufs, framework.Overlay, framework.Overlay2, framework.DeviceMapper: needsBaseUsageCheck = true } pass := false // We need to wait for the `dd` operation to complete. for i := 0; i < 10; i++ { containerInfo, err := fm.Cadvisor().ClientV2().Stats(containerId, request) if err != nil { t.Logf("%v stats unavailable - %v", time.Now().String(), err) t.Logf("retrying after %s...", sleepDuration.String()) time.Sleep(sleepDuration) continue } require.Equal(t, len(containerInfo), 1) var info v2.ContainerInfo // There is only one container in containerInfo. Since it is a map with unknown key, // use the value blindly. for _, cInfo := range containerInfo { info = cInfo } sanityCheckV2(containerId, info, t) require.NotNil(t, info.Stats[0], "got info: %+v", info) require.NotNil(t, info.Stats[0].Filesystem, "got info: %+v", info) require.NotNil(t, info.Stats[0].Filesystem.TotalUsageBytes, "got info: %+v", info.Stats[0].Filesystem) if *info.Stats[0].Filesystem.TotalUsageBytes >= ddUsage { if !needsBaseUsageCheck { pass = true break } require.NotNil(t, info.Stats[0].Filesystem.BaseUsageBytes) if *info.Stats[0].Filesystem.BaseUsageBytes >= ddUsage { pass = true break } } t.Logf("expected total usage %d bytes to be greater than %d bytes", *info.Stats[0].Filesystem.TotalUsageBytes, ddUsage) if needsBaseUsageCheck { t.Logf("expected base %d bytes to be greater than %d bytes", *info.Stats[0].Filesystem.BaseUsageBytes, ddUsage) } t.Logf("retrying after %s...", sleepDuration.String()) time.Sleep(sleepDuration) } if !pass { t.Fail() } } cadvisor-0.27.1/integration/tests/api/event_test.go000066400000000000000000000043521315410276000223630ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package api import ( "strings" "testing" "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/integration/framework" "github.com/stretchr/testify/require" ) func TestStreamingEventInformationIsReturned(t *testing.T) { // TODO(vmarmol): De-flake and re-enable. t.Skip() fm := framework.New(t) defer fm.Cleanup() // Watch for container deletions einfo := make(chan *info.Event) go func() { err := fm.Cadvisor().Client().EventStreamingInfo("?deletion_events=true&stream=true&subcontainers=true", einfo) require.NoError(t, err) }() // Create a short-lived container. containerId := fm.Docker().RunBusybox("sleep", "2") // Wait for the deletion event. timeout := time.After(30 * time.Second) done := false for !done { select { case ev := <-einfo: if ev.EventType == info.EventContainerDeletion { if strings.Contains(ev.ContainerName, containerId) { done = true } } case <-timeout: t.Errorf( "timeout happened before destruction event was detected for container %q", containerId) done = true } } // We should have already received a creation event. waitForStaticEvent(containerId, "?creation_events=true&subcontainers=true", t, fm, info.EventContainerCreation) } func waitForStaticEvent(containerId string, urlRequest string, t *testing.T, fm framework.Framework, typeEvent info.EventType) { einfo, err := fm.Cadvisor().Client().EventStaticInfo(urlRequest) require.NoError(t, err) found := false for _, ev := range einfo { if ev.EventType == typeEvent { if strings.Contains(ev.ContainerName, containerId) { found = true break } } } require.True(t, found) } cadvisor-0.27.1/integration/tests/api/machine_test.go000066400000000000000000000034701315410276000226460ustar00rootroot00000000000000// 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 api import ( "testing" "github.com/google/cadvisor/integration/framework" ) func TestMachineInformationIsReturned(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() machineInfo, err := fm.Cadvisor().Client().MachineInfo() if err != nil { t.Fatal(err) } // Check for "sane" values. Note these can change with time. if machineInfo.NumCores <= 0 || machineInfo.NumCores >= 1000000 { t.Errorf("Machine info has unexpected number of cores: %v", machineInfo.NumCores) } if machineInfo.MemoryCapacity <= 0 || machineInfo.MemoryCapacity >= (1<<50 /* 1PB */) { t.Errorf("Machine info has unexpected amount of memory: %v", machineInfo.MemoryCapacity) } if len(machineInfo.Filesystems) == 0 { t.Errorf("Expected to have some filesystems, found none") } for _, fs := range machineInfo.Filesystems { if fs.Device == "" { t.Errorf("Expected a non-empty device name in: %+v", fs) } if fs.Capacity < 0 || fs.Capacity >= (1<<60 /* 1 EB*/) { t.Errorf("Unexpected capacity in device %q: %v", fs.Device, fs.Capacity) } if fs.Type == "" { t.Errorf("Filesystem type is not set") } else if fs.Type == "vfs" && fs.Inodes == 0 { t.Errorf("Inodes not available for device %q", fs.Device) } } } cadvisor-0.27.1/integration/tests/api/machinestats_test.go000066400000000000000000000031751315410276000237270ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package api import ( "testing" "time" "github.com/google/cadvisor/integration/framework" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMachineStatsIsReturned(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() machineStats, err := fm.Cadvisor().ClientV2().MachineStats() if err != nil { t.Fatal(err) } as := assert.New(t) for _, stat := range machineStats { as.NotEqual(stat.Timestamp, time.Time{}) as.True(stat.Cpu.Usage.Total > 0) as.True(len(stat.Cpu.Usage.PerCpu) > 0) if stat.CpuInst != nil { as.True(stat.CpuInst.Usage.Total > 0) } as.True(stat.Memory.Usage > 0) for _, nStat := range stat.Network.Interfaces { as.NotEqual(nStat.Name, "") as.NotEqual(nStat.RxBytes, 0) } for _, fsStat := range stat.Filesystem { as.NotEqual(fsStat.Device, "") as.NotNil(fsStat.Capacity) as.NotNil(fsStat.Usage) as.NotNil(fsStat.ReadsCompleted) require.NotEmpty(t, fsStat.Type) if fsStat.Type == "vfs" { as.NotEmpty(fsStat.InodesFree) } } } } cadvisor-0.27.1/integration/tests/api/test_utils.go000066400000000000000000000042711315410276000224020ustar00rootroot00000000000000// 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 api import ( "testing" "time" info "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/assert" ) // Checks that expected and actual are within delta of each other. func inDelta(t *testing.T, expected, actual, delta uint64, description string) { var diff uint64 if expected > actual { diff = expected - actual } else { diff = actual - expected } if diff > delta { t.Errorf("%s (%d and %d) are not within %d of each other", description, expected, actual, delta) } } // Checks that CPU stats are valid. func checkCpuStats(t *testing.T, stat info.CpuStats) { assert := assert.New(t) assert.NotEqual(0, stat.Usage.Total, "Total CPU usage should not be zero") assert.NotEmpty(stat.Usage.PerCpu, "Per-core usage should not be empty") totalUsage := uint64(0) for _, usage := range stat.Usage.PerCpu { totalUsage += usage } inDelta(t, stat.Usage.Total, totalUsage, uint64((5 * time.Millisecond).Nanoseconds()), "Per-core CPU usage") inDelta(t, stat.Usage.Total, stat.Usage.User+stat.Usage.System, uint64((500 * time.Millisecond).Nanoseconds()), "User + system CPU usage") // TODO(rjnagal): Add verification for cpu load. } func checkMemoryStats(t *testing.T, stat info.MemoryStats) { assert := assert.New(t) assert.NotEqual(0, stat.Usage, "Memory usage should not be zero") assert.NotEqual(0, stat.WorkingSet, "Memory working set should not be zero") if stat.WorkingSet > stat.Usage { t.Errorf("Memory working set (%d) should be at most equal to memory usage (%d)", stat.WorkingSet, stat.Usage) } // TODO(vmarmol): Add checks for ContainerData and HierarchicalData } cadvisor-0.27.1/integration/tests/healthz/000077500000000000000000000000001315410276000205365ustar00rootroot00000000000000cadvisor-0.27.1/integration/tests/healthz/healthz_test.go000066400000000000000000000021741315410276000235670ustar00rootroot00000000000000// 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 healthz import ( "io/ioutil" "net/http" "testing" "github.com/google/cadvisor/integration/framework" ) func TestHealthzOk(t *testing.T) { fm := framework.New(t) defer fm.Cleanup() // Ensure that /heathz returns "ok" resp, err := http.Get(fm.Hostname().FullHostname() + "healthz") if err != nil { t.Fatal(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal(err) } if string(body) != "ok" { t.Fatalf("cAdvisor returned unexpected healthz status of %q", body) } } cadvisor-0.27.1/integration/tests/healthz/test_utils.go000066400000000000000000000011621315410276000232640ustar00rootroot00000000000000// 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 healthz cadvisor-0.27.1/logo.png000066400000000000000000002241671315410276000150740ustar00rootroot00000000000000‰PNG  IHDRµbÙ w(>IDATxÚìÝ÷_\סïýç߹綧ܸ;.q‰;‰§Ú‰sâä8‰SÓlKôÞ« z Ì Cï†245:Dï3ø‡„C$Y†){¯½÷çýúþà›cÃfí5{½î—ÍZÿ×§¨ÅÎ΃@˶¶¶àôþ/† míÆ2ÓêÚC@kæænffåŒ]¹ÊP§G?PÝÝÝ€ o_SyÅÚú:@ ænÞÌÊÎýä¼w|âF8úq€ªÔÕ7|rÞÛo_ÿŠÊª Æ€ZÍÏÏçäæŸóò9zî 0&À™ÐTekkËÇ/à¨*rÄñÏ•UÕ›››Œ 5¹}ûN^~áq3îHdTÌáá!#œ ý8@mÊLåÇ…ÑÉ–œwɨÀíÛ·ïkÆbîîap€³¢¨Íò½{ç½}ïkŽhÉ(Ýç5㎇0DÀYÑT(7/ÿÁþˆ–€B=¢?Jm]=£8~ B“Ÿ×"·ä•Uëë댑ÍÏA3ßÚÚc8~ Nq ‰®ÈZrSyÅ*½ñܼy+'7ÿÑÍøQ ‹Š.À9ôãu2w÷|a©to_c™éÞÊ ƒ@33³™Y9§|‚9255Í Ρ¨ÓÞÞž_@Ðé &/¿½ayy™¡ —ÉÉ©ôŒ¬Ó?¸‰OdܧÑTËXf:SÍäÈyoßâ’ÒÅ¥%F€”ÆÇ'RÓÒÏúÈr¤ËÜÍèN£¨ÖÍ›·œ(›ŽZò‚¢ùùÛŒ!O»rõbrªs+o_ÿÆpý8@Íâ.8×:9rÎË';'ofv–aàv‡‡‡Ö¡aWžQŽ\ædNÀ5ôã5kïèt¥{:Ê¥´Œ7ÆLna·Û-}ýÑ1q®?x4.¢¨Ùææ¦—Ÿë%”#.&Ž]aH8íàà ËÜå–‡RXDÔáá!£ ¸‚~ r¹yn©¢ŽŸØ?0h·ÛX§·³³ÓÜÒîÆÇQ]} ¸ˆ~ rcW®º±:JxDTg—yŸáðhëëëÕ5µ~Aî} óòY^^fxÑTÎn·…„¹½"w$08´¾¡qkk‹Að ÅÅŽÁÛ×ßÏŸ”Ô4Fpý8@ýŒe&OôSGñõ,¯¨\Y]eœ™›ËË/t<<÷äé2w3΀ëèÇê799å¹–ê(^>~Eº’ùùÛŒ6 eW®^»”–áéÎyoß Fpý8@ýCÂ"<ÝX%=#ëúõŒ9 )½–¾˜Øxiž3i™Œ9àôãM(¯¨”¦·:Jl|b_ÿ€ÍfcäuÛÚÚjhlòÐ!Ÿ—ž^ #¸ý8@ffg¥l¯ŽÞÔܲ½½Íø곸´d,3ùøHü`ñòñ㩸 ý8@+Â#¢¤¯Èð,3•/..r u˜˜˜ÌÉÍ÷èñ›Hfv·púq€VTVUËRf圗OVvîãÜ@¡,}ýq d|’8â¸îà.ôã­˜œš’·Õ:JLl¼¹»gŸ;(ÅÚúzm]}`p¨ìóÞ¾›››ÜÀ]èÇZa·Û‚BD¨Èq\IuMíÊê*÷ÙÜÜÍËÅ:/?AÉ)—¸)€Ñ4¤HW"HÉuü*h^~Áøø·ÊÁÁAÿÀàÅäT¡žŽ´´¶qw7¢hÈÐðˆhm×Q¢câÚ;:···¹G€¼–ïÝ«®©a+•‡faá.÷p#úq€†ìîOƒññ (ÑnÞ¼Å$vxx8våjVvîyo_a‘Q1Ü)À½èÇÚ’ž™%lùuœ “-}ýœá H`ss³¹¥5<2Zü'CEe÷ p/úq€¶tv™ÅoÁŽâl,3ñ:9à ‡‡‡W®^ËÍ+ùoJîËøgnF?Ж»wï*¥ ;NÂ…¤.s÷Îηpݽ{÷êêBÃ#•õðñ 888àöîE?М°ÅUäGíX‘®drrŠ;8Áf³Y‡†Ó3²Îyù(ñ ‘™ÍMÜŽ~ 9º’R%¶cÇ‰Š‰kjn¹wï·R‡‡‡»»»[[[›››«kk+««÷VV–weeÅñÏŽÿem}Ýñrü ÛÛÛ{{{ŽÿÄñ2z²˜+3•…(úƒßÖÞÁ­ÜŽ~ 9ƒVE×dG9çå“’šÖÝÓ»½½Í==“ÃÃí­­Å¥¥¹¹›7nŒ ôôZZÛÚëê**«J ÆÂ¢â으Դô¤‹)±ñ‰Q1!aA!Þ¾þ.¾z|ÞÛ×Ç/Àñ¥_02:6.á‚ã[\JËp|;Ç7u|kÇÔ74¶wtZúúGFÇ&&&oÝš_^^fw'8Æ­¡±É1Î*ø¼;2?›{ ¸ý8@s666ºÁÂCãåã—›W0<2ÊÞÄGö÷÷—–—§§gFFÇÌÝ=õ zcYn^~JjZtl|`pèyo_%ÞhÇe‡†ÇÆ'¦^JÏË/(3•765w÷ô:~Ì™™Ù{++6›»ï°½½í–ä”KªùŒ;Æ<~ Eq ‰jêÎŽâ\¢7\¿~Ãn·«þ,.-ݸ1Þké«ohÔ•”fdfÇÆ':A}wöô OLÊÎÉ3Ëš[Z­SSÓ+««ZØÚeww·`Ðñ³{ûú«ïÎóÜ<~ E•Uê.IK Æë7n¨ã…â©éé¾þú†Æ"]IrÊ¥àÐp5ý€4GF§¥g:&FsK«uhxîæMuìÌãø)úûÔZ‹Çbéã¹ xý8@‹FÇÆ´ÐŠú_.Ö ìîî*â¾ÌÏß´5469®<1)Ù/ ˆvÛ£¿JIN¹¤+Õ7·´ŽŒŽÝ]\Tʬ®®v™»ÓÒ3½|ü´p§–––xnž@?Т­­-M½€ìåã—ž‘ÕÑÙ%TËf³Ùæço Z«kjsró#£cº3¸Ê¦Jl|bAaQ}CãðÈèâÒ’8{³8®dvv®®¾!>1IS7%84œ‡6à!ô㊉KÐfc,3ŒŽíììH<æ›››7nŒ·´¶ÇÆ'jäÍ_¥ÇÇ/ ébJ©ÁØÙežžžÙÛÛ“xÚ¬¬¬ôZú ‹‚B´y /óÄ<„~ Qzƒ‘=©.\¬¨¬ÛÜÜôÄ oll\¹z­¾¡1'7?4<’®YÓ&:6þr±®­½crrÊC[÷,//÷õ”è Q1ŒyGgOlÀCèÇeéë§w»ï½òËźŽÎ®©©i§_-ßßßwüçmí…Eá‘ÑŒªêsÎË':6¾¸¤´ËÜ=7wÓéíËWVWÇ®\mljÎÎÉ c`OÆ1°<±¡hÔÝ»wéÝ‘°ˆ¨Ì¬SyEGg×ÈèØÌÌ콕•½½½“»Q;þyssóöíÏNÔtü› .²e ›±¤^J¯®©»²¸´tß/Zìvûöö¶ãŸššnim+ÑRRÓ4»qÊiâíëo³ÙxbB?Ð.ÿÀ`Ú7ç:P_ÿ@o_MqJœÞÅ1UŽ& £áD’S/ñ¬<‡~ ]éY´o„‘S^Qɳðúq€vU×ÔÒ¾BDÎÀÀ ÏjÀsèÇÚ5<M!D´ÔÔÖñ|<~ u•U4q„ÑÒkéãù xý8@ëÚ;:iâ!¢åÆqžÏ€§Ñ´nxx„&Ž"Zîò|<~ u3³³4q„Ѳ»»Ëóð4úq€ÖÝ[Y¡‰#„¿΀èÇZ···GG*á‘Ñ<œ Ðð©_}!Dœ\¸˜Ì“ý8Ÿ†…GÒÇBÄIfvOf@ôã|—@G'…E<™ ÐðéÅäTú8Bˆ8)5y2 àÓôÌ,ú8Bˆ8©¨¬âÉ H€~€O³rréã!⤺¦–'3 úq>ÍÍ+ #„ˆ“ºúžÌ€èÇø´ ð2}!Dœ446ñd$@?À§yù…ôq„qRßÐÈ“ý8ŸæäæÓÇBÄIMmOf@ôã|š™CG'UÕ5<™ ÐðiJj}!DœŒe<™ Ðði\B"}!Dœód$@?À§¡á‘ôq„q’™•Óý8@ë½|üèã!â$.ág@ôã­[__§Œ#„•€ ΀èÇZ7;;GG-»»»<ŸO£h]__?M!D´ÌÍÝäù xý8@몪khâ!¢¥¯€ç3àiôã­KMK§‰#„ˆ–2S9ÏgÀÓèÇšf·Û}ühâ!¢%>1‰G4àiôãM›žž¡†#„˜óÞ¾[[[<¥¢hZuM-5!DÌ ò”<Š~ iQ1qtp„1“›WÀSð(úq€vMNNQÀB„—ßúú:ÏjÀsèÇÚ•_PHG9õ <«Ï¡hÔüüís^>´o„‘ã¼½½Íðúq€F¥gfQ½BÄOEeOlÀCèÇZÔké£t#„("ç½}§§gxnž@?М¹¹›>~”n„¥$$,bum§7àvôãm™˜˜  ¡n#„(+Q1qwy†îE?Ðæ–ÖóÞ¾m„%Æ×?phx„'9àFôãMØÛÛËÍ+ _#„(=•UÕ‡‡‡<Õ· ¨ßâÒRL\µ!DIËÈÜÚÚâÙ¸Ž~ rW®^ó ¦P#„¨)‘Ñóó·yÂ.¢¨Yk[;ŽBÔºùÈè(ÏyÀôãu²ÙlºR= !DÅ9çåÓÔÜÂpý8@…¶¶¶R.¥ÑB´¢bÍfãÉ8~ 6ËËËQ1qTf„í$õRúöö6Ïà¬èǪ23;JYFÑZbbãï­¬° gB?Pѱ1¿j2Bˆ6~óÖ-ÖàôèÇ*ÑÓk9ïíKAFÑrü‚Æ'&X€S¢¨ASs‹‹0_ÿ€ˆ¨èĤ‹é™¹yù—‹ŠKõ½Áh,39b0–9þ¹¨X—_P˜™•”œBHšs^>A!a1qñÉ©—²rr /ëJSÞè˜êGsÞñϺR}á墜¼üÔ´ôøÄ a‘ŽÏ‰ÖÊñ# °.§A?P¼ŠÊ*µ¿˜”œR¬+©ohì˜\^^ÞÛÛ;tŠÍf[[[››imk3ËÒ32C©_‰8‰ˆŠÎÊÎ1•WttvŽŽŽÝ¼ukccÃn·;7çwvvî..Þ¸1Þk±ÔÔÖ^NHLRwo~ÞÛ·»§—ÕøBôã;<<,5Õ×m‡dçæÕ74Ž]YYYqº<“­­­‰ÉÉöŽÎb]ITL,-‘2q ‰c™¹»gvvvwwW‚ o³Ùî.. W×Ô¦gdúúªïuû¶ö– àÑèÇJe·Û/ëTSf…GF—”ê{-} wïJSˆ?ÚúúúÈÈheUuÒÅÚ[âöxùø]J˨«o¸~ýÆÎÎŽìÞf³Ý¼u«£³«àrQPH¨jƹ¡±‰Åxúq€"Ùíö¼üBT„é™mí wï lccc``ðrQq@Û——e0–ŽŽ‰Ð‰ÇãeîæÍ†Æ¦¤ä”s^>JóêšZ– àóДGé帷¯~AáÐа4ûH¸÷Û©©)Syû•“3%*:¶¶®~þöíC¥ÙÜÜì鵤gdž÷ö¡"Ô‡~ 0Ê-ÇÏ{ûææå[­CŠ«Åú‚íÄĤ±ÌLùKñ¶xMmÝ­ùùCåÛØØ0w÷¤¦¥+ô^8nËð úq€’æ(¯ŒŠini]__?TýýýÁAkZz]09ù« ¼ü‚«W¯Ùl6õÍù»‹‹UÕ5A!aŠ»/µuõ,"À}èÇJR¤+QPuÎË'/¿`||B„ó6%( +«ªý‚h‡µœ°µþ*è>Ã##)—Ò”uƒšš[XG€“èÇŠ¡7–)¥„òñ (3•/.-jÌÎÎN[{GXDM±ÖŸp¡¿ààà@ksþæÍ›—‹Š´;yGg« pŒ~  •UŠèž‚‚›š›·¶¶5Ìf³ Z­qñ‰´ÆZÈ¥´Œë×ohá$áÞ½{å•Þ¾þŠø»–^Kk p„~ -­mâ·N~A M;;;‡øçžƒƒÖè˜8dµæbJêøøSýØêꪱÌäåã+þñ£cWXY€OéÇâëëë?çå#øn*5µugüï’÷÷„GFÓ&«)‰.^½zéýPË÷î•”êÅjMMO³¾ôã¡]¹zí¼·Ð/cëVWWém¿©¹Å×?fYù'p†[úú5¾›ÊiÌß¾–‘)ò­ô ¾sç« 4Ž~ ®[·æEnT“.¦ÌÌÎÒžÞúúzI©žŠY¡ñöõ«©­ÛÝÝe&ŸÞèèXdtŒ°÷4,"Êñ©d­–ѵºº!ì!œ½–>^¡uÎÜÜ\|Âêfe%#+{yy™ÙëÜO465yùø‰yg.\ÜÛÛcÅfÑD´»»—(fTPxy}}ÖÏMÍ-Þ¾~ôÎâ'08¤¿€I뢅……äÔKbÞâìœ<Dzî@›èÇÂ9<<ÌÊÎrç刱±+4}îrwq1õR:´È¹\T¼±±Á\u »ÝÞeîsϨʪj–hý8@8UÕ5öGù…[[[t|no [ZÛ¼||i¢Å;¼1ÈjbŠºÝÒòrRrŠ€w¼¯€ÕD?KÿÀ hµ‘_@O¯…^ÏsnÞ¼G%-NR.¥­¬¬03=Äf³ÕÖÕŸóòìüUÿ™ÙYÖ h ý8@ óóó>~‚^—t÷î]=OÛÝÝ-)ÕSLËžs^>MÍ6›9éiSÓÓ¡á‘¢m!µ±±ÁJM¡ˆb{{;"2Z¨¶HWRº··G‘'™^‹…C;e=Š3ôÆø8óP2i™BÍÔ´t»ÝÎzí ˆ"3;Gœ’ÈËÇÏÜÝC'Ë^+aQTÕÒçÂÅdöT‘e¯•êšZ¡fBEeë´ƒ~ „¦æ¡6˜›£¹“Ëææfº`/Õª>¥zÃþþ>sO.##£¾þm-5:6ƪ Èojjú¼·¯ ÅP|Â…ÕÕU ;Ù_ª-Õ¨­¥IKkSNþ¿œ¸u+$,\)²²²ÂÚ- ÈlkkKœCê2³²wvv¨ê!Ô_¨2Þ¾~CCÃÌ4A¬¬¬ÄÆ'27.&§²9´€~ ³ìœ1Áúõ¡Hjm}Ý/@æ-¤só ìv;¥›¢õôöÒzŸ2^>¾“ÌEÛÙÙ‰O¼ ÷É®q6›U *C?T¾Ü$]LÙÛÛ£nSšÚ:ºïÓd`pÙ¢«««¡áòÎ¥ú†FV1¨ ý8@:×¯ß·Ü Œ^__§hSÍÑ……—‹¨¿Ʀ&¦ŠjÌß¾í(ãtòöõ_^^f-ƒšÐ$b·Û£câdlvü¨ØT¶/³g½ ›¢b“De®^»&ï¤ÊÎÍc9ƒšÐ$ÒÖÞ!o­322J¹¦ÊM'‚BB©ÂL|â¶R¥Æ¦fy§Öµë×YÑ ôã)lnnÊ{,guM-µšZMLLž÷ö¡?™€ à¥åeæ†ZwÊÎÍ“qvEÇÆ;®u ê@?‚±Ì$c›“ž‘i³Ù¨ÕTLö¿N-W¯]cV¨ØöövTt¬Œ¬ËÜͺu xÜâ⢗Ÿ\=NphØÆÆ…šêåäåS‹¥¦¶Žù zóóó2>WƒBÂöööXÝ ôãËÍ+s«ÜkשҴ`ss384œrüBRòÁÁóA6Ñ.ãL«­«guƒ Ð7oÝRSf³ÙFGF ó >ùèãï~ç­§âñÿó%çòúk_ûýo—’œl6›777Õ4JûûûÑ1qÚ,dz²sTVþÎÎΚŒeAïüðGÏ<ù”ÓÞ‘7¾þúŸ?ü0'+{ppPe¿6´Zåšr>~¬tP4úq€¥gdÉUÜTUר¦¿=?_S]óÛ_àJ'þyyú‰'ùïgefNNL¨cÄ&&'µ¹³Šjþ2`mmmÈjûÆë¯»}Â;òü³_þðÿY\T¬š½h²²säšx啬tP4úq€§Ìݼ)We³··§¾½VVVŠ/½ûãŸx¢4täßû~jrÊ͹›J(½Á¨µ~¼³«K•û‡ŒŽŒF„…¿ô‹ž˜ðO>öø¯ßÿeiIÉæÆ†ÒŸ ¾þríB¾µµÅzå¢xJ^~\]á¸Z^…þ<=ÝÝ¿zÿ}µäO|é±ß~ðAC}ƒÝnWèøloo‡„…k§OJNQîÍ:ͬÌÌW^zÙCsþùg¿àçåÊåQg—Y®éWWßÀzå¢xÄÝ»wÏyùÈRÖéJµ¡¡¾á¯¿î¡ÆÐ‘7¿ù­Ë……;;;Jœ¡¡a”ãŽÚüíÛZ˜ð«++Až›ðŽ|ð«_wvt*qpìv{bÒEYf`@PÈÞÞ«Š~àrmpáë°¶¶v¨›Ÿ|ô±Gï}õ«— •¸_MÊ¥4-ôãÆ2Ó¡–ôtw¿öÊ«óïýôßÍf³âFfzzZ®IØÑÙŪ…¢¸ßÖÖ–_€,5MSsó¡ö\.,|âKy´1üÆë¯×TW+kXnݺ¥úrÜ?0hssSk~qqñ½Ÿþ»G'¼#¿ùõãããʙ¢byŽ|ˆŠq|wÖ>(ý8Àýšš[déhÂ#£÷÷÷5©¡¾þé'žôtcø‹÷Þ»qㆂ†¥ToPw?Þ®ÌÍ@ܲÅüÿOOø§"<,LA¿XYYññó—e*Ž]¹ÂÚ%¢¸™Ýn ”¥ ± jXSc£§ß"wäé'žLºpA)Û­¬¯¯ûú¨µŠŽ=88Ðì„ßßßÿÝ¿ñô„w䯿ÞÖÚª”a©«oe6¦¥g²üA‰èÇn6:vE–v&.!Ñn·j[‰N'A]èÈÛ?ø¡R¶ž¨©­Sk?>08¨ñ ¿¹¹ùî;?–fÎ*â¬ÚíímÿÀ`YΉ]ZZb„âÐÜ,3;G–®ptt쇇~>¾ÒÔ…_~ú™Ë……âÈÖÖ–_@ úÊñ˜¸x›ÍÆ„¿uóæWžAš9ÿ½·Þº~íšþ”¤¹Y–9YU]à Å¡¸ÓÊÊÊyo_é{™Ä¤‹¼<~üöè[ßþŽ4u¡#ç>þDü—jëÕ× 3Ûÿqëê$›ðÏ=ólUe¥àâøH‡H?'ƒBÂl6ë ”…~àNMò¼·xõê5ZÂc=ÝÝ’Õ…Ž¼óÃݹ}‡'ØMHFüý樂ó1QÑ‚HKk›,3sdtŒuÊB?p§¨˜8ºB|ü·¤¬ _íkW¯^erÉ28he’Ÿ477÷ôOJ9çÿò§?‹ü—Žkó ’~fææ°BYèÇn3;;'KWØß?@?xŸ‰ñq)»BG^|îù>KŸ°²¶¶æåã§Žr<,"òàà€I~??‰çü/Þ{occCØ©ª®‘~r:>eÛÛÛ¬†Púq€Û”™Ê¥¯cBÂ"è êÏ~(q]øÜ3Ïvuv ; ºR½:úñ¶ö¦÷ƒf¦§%žð޼ûÎWWVÄÕÕU/Nƒèîée5„‚ÐÜãðð084\ú.¦¥µfð¡Z[Z¥¯ Ÿ}êéns·˜rûΔã~Aâˆ*—_þÇû²Tä¾E^¤+‘~Ц¥g² BAèÇî155-}ãã翵µE-øPûûû¯¾ô²ôuáóÏ~yppPÌ1IÏÌRz?^^QÉÜþï%K]øò‹_™ššp@††‡•Þß¹s‡‰ýy–—d™ðŽüñ÷°ÙlŽIÂ…$égiO¯…5JA?p°ˆ(é[1KXq˜ŒerÕ…o~ó[ËËË¢ ÈÁÁAPH¨rËñä”KN³ÍÍMq.æßû¾\s>48DÀ»cîî‘~¢ffå°&B)èÇnpû¶ ;;ÇÄÆ ØFY­â\Ìää¤\]¡#¿xï=N­¬ªVn?néënW÷Û·…zo:ÀÏ_Æ9¯+.ííììøøH5<4$ڜו꥟®cW®²2BèÇnœzIúþemmM¨jáÎW¾òÒäÄ„PWõío}KƺБºÚZÑêÂĤ‹JìÇu%¥¢dpPÐ_þôg¡.©¹©IÞ ÿÆ×_íÑtc|\úéj0–±2BèÇ®ÚÝÝõòñ“¸|I½”.ZWøë_þêñÿó¥ÅÅE¡®êÝÿDÞºð¥^¼s[¬#%[ÛÚ”Ø_½vM¨alinvÜ_??¡®ªÏÒ'ï„w䣿þU¨1±ÙlÒo»ÃâE ¸êêµkÒw…]æn±vuÈùÇ®BUøYkÿþ/e¯ × Ô˜,ß»§¸r< (X¨ÍÜ———_}éeÇÍŠˆêæ^¹rEö ïHEy¹PÃb,3I?iWVVX!>úq€«¤?ððœ—Ïúúº8ÝÓÜÜÜsÏ<{Ô‹mmm øV»ìÑ—ê…–¤‹)ÊêÇKõ¡ðÜÇÝÙ¨H±úñ«W¯Š0á_ùÊKËËËâ ËÄĤô“¶¯¯Ÿõ⣸*á‚Ô»9§¦‰µ¹Ê¿úõq/&Úþ*?ýÉ»"Ô…/½ð¢P#ÓÖÞ®¬~üÚµëâŒ^{[Ûñ ðójÂ÷÷õ‰0áùä£Å»Ý&ñ¤-.)e}„øèÇ.ÙÛÛ;ïí+qíÒÚÖ&NñTW[{²›œœª.|ëÍo Rúxy‰3, *Ç}üü÷÷÷:Ç•|ç[oßÖ¿ýå/‚íŠÞ"È„wÄb±ˆ32Å%¥RoAË ñÑ\21)ßíß¾#Êy;;;ß|ý“XkK«8ØÁÁÁÓO<)HWøÄ—gpÂ#£”ÒgåäŠ3nÙYY'oë;?ü‘Pýx^n®8ýø;?zÛn· 22Ö¡!é§îææ&«$G?pIsK«Ä…Kh¸@ûgfdÞ׈åfçˆsy3ÓÓât…ŽüÇ{?gp Æ2¥ôã]f³ ƒ¶ººúâó/œ¼§Ï=ó¬8°Cp` PsÞTfdd¶¶¶ÎyùH88øúW_°.çЄ Iâ÷ãźA†ëRJêCoèo?ø@˜?˜¨pÂ÷;o 2>×oÜxö†„E°PBpôãçI8gx¤(ïÒ–ètŸW‡…‡†Šp…æ®.»BGþøû?rMåâ÷ãÝ=½"ŒÕîîîW_~åóN^]XXá"÷ÁoÄœó-ÍÍ‚üVOú-È9¢‚£8¯ËÜ-qÕRT¬¤Zýþ[ßý¼.ì忲½½-û~ü·Äì ŸøÒc³33"ÜDëÐøýøí;w„ø]‚±ì÷4#-]ö+œŸŸwL-1çü¯ù+Qþf"Qê¿™˜œšb­„ÈèÇΓþõ[swÓÀÀÀ£ë0Ùwdž™¶+t$!.^„û¸ºº*x9î,Èvö?ÿÙ{¸¡_}ù•y¯0<4TØ ÿÙï„fgE¸e¦r‰çpO¯…µ"£8/#3[âªåÖ­["tL¾Þ>®Ã^íkò¾Bþ…W(o¾öêWD¸•!a"÷ãYÙ"ŒÒÄøøÞÓü¼<¯páÎgŸzZä9ŸŸ Æïö%žÃ•U¬•ý8Àyá‘ÑRö,^>¾"”ª»»»/|ù¹/¬Ã.$&Êu…CV«ÈEáQ:;:E¨ ³srEîÇ«kjE%ÇdþÂúâó/,-.Éu…ýõo‚Oøo¾þ†·òÎ;Ïᬜ\ÖJˆŒ~à$»Ý~ÞÛWÊž%!1I„‚©±¡ñ4uØÓO<91>.ýåííí½ó£·ÅïÇý}}E¸›uõ "÷ãÖ¡!FéÛßúÖiîé'},Ëåu´·‹?á²Ze¿•6›ÍÛ×_Ê9—Àr ‘Ñœ´²²"õ᜺ºÂsrÊ:ì‡ßÿô›2GED*¢+|é…Eøk€¡¡a‘ûñ;ÎyåÊ•ÓßV£Á õÎ* ¯¾ô²"æ|tT”èôõd¹„ÈèÇNššž–¸+lniáíË—_üÊé1¯sç¥<_±¶¦FEáQúûe¿¡·%ßnâô9ï-ĆBéii§¿§_~ú™±ÑQ)7;ú÷~®” ÿ½·Þ¡/*ÖI<“···Y1!,úq€“¬Ö!‰K–ÑÑ1Ù«%ëà™·öŽŽ‘æÚÌfóÓO<© ~üB‚ü'îïïŸóò³މ¡N=kýêK/ÏLOKpaýó_4á¹uó¦ü;D55K<“o߾Ê aÑœÔÑÙ%uÉ"À^—RR(Åâãâ<ýyWgçsÏ<«¬®ð§?yW„þ7<2JÌ~<+'WöÁÙÚÚrâ—.o|ýõÉÉIOï³îã•5á1èõòÿ’Oò_mŽO°bBXôã'ÕÔÖI\²ìííÉ^-ýîƒß8׋ùùøzîú++*”õæøñ¦ÒïÏþ ´ŒL1ûqSy…ìƒÓÓÝíÜÍ}å+/ zèªÖ××þ$ÊÇs@ö{:wó¦Ä3yÐ:ÄŠ aÑœTj0JÙ°…„ÊÞ+ÙíöŸ{ÞéjìÝw~<77çÞKÚÙÙ  RbQx‹Å"ûm-)Õ‹Ù·µw(ô&ŽòÔãOäde»ý/'ÆFGßüæ·:áßzóÛ²ßÓ ‰grGg+&„E?pR^~¡” Kâ…‹²÷JSSS.¶cÏ?ûåÜœwºØmîþîwÞRn9îHnvŽì·µ¾¡QÌ~|hxXöÁùÓþ§‹·ø×ïÿrrbÂ-³½½}!!A‰*qœ'¾ôØÆÆ†ì¿çóöõ“r&×Õ7°bBXôã'¥gfIÙ°ääæÉÞÖÕÖº¥#ûÁw¿W[Sk³Ùœ¾’+ccþðOŠnÆâãå%ÿ"½½böãÓ33²Î·Þø†ëwù©ÇŸ ¹uë–Ó—±»»[ZRòÆ×_WÁœý¶FFÅH9“+*«X1!,úq€“.&§JÙ°è FÙK¥‹’Ü»ÓBVfæÂYÝÞÞ®ª¬üͯ?PAKx”w~ô¶ì·ul슘ýøÒÒ’¼#³¹±ñÄ—s×½~êñ'Îr®«³óL?13=œtñë_}M5s^W\,ÿ£LÚ§w©ÞÀŠ aÑœ— eÃR[W/{©ôÉG{b¿…ŸþäÝ„¸ø–æ–Ù™™«ÃÅÅEKoonvÎ~÷»çžyV5-áñ†3²ßÖ™ÙY1ûqÙ/½råŠ'núW_~åÜÇŸèKõ£#£›l6âø8>ÍMMÑQQoÿà‡*›ðŽ8~.Ùç|n^¾”3¹ °ˆ¢8)2:VkgþìÝŸzº;{ú‰'_}éåoë[o}û;_ûêWÕWˆ?˜{÷îÉ{[ï.. XŽ{ùøÊ>á%˜¯|å¥7¿ñÍ|ïûo½ùí×^yUÑÛ‹Ÿ&ùÓŸµv&mn^>+&„E?pRxD”” K¯¥OöRéµW^U}[-}FGFå½­öãA!¡²Oø¼Ü\æ§Ûóã·åßS¨¢²JÊÉœ•“ËŠ aÑœ!eÃ2<2"{©ôäcÓî¹=ímmòÞÖýý}ûñÈèÙ'|B\<óÓíyýµ¯É~g›¤œÌ™Ù¬˜ý8ÀIA!aR6,W¯]“·QZ]]¥ÚóD*ÊËe¯ ÏyùˆÖÇ%$Ê>,þÌO·çÙ§ž–ýζ¶µK9™Ó22Y1!,úq€“$~|brRÞFivf†jÏÉÏË“½.ôõ­OJN‘}Xþúç¿0?=‘íímyïl—ÙÌûãÀúq€“Â#£¥lXfffäm”nܸA¯ç‰d¦gÈ^…ˆÖ§\J“}Xþð»ß1?=‘Õ•yïlOo/ûGèÇNŠŠŽ•²a™›“·Q¥×óDR’“e/‚ƒBBEëÇ/¥Éÿkƒ~õkæ§'r÷î]yﬥ¯OÊÉœ›WÀŠ aÑœ— eÃróæMy¥!«•^Ϲ(ÿFÛÁ¡a¢õã陲Ëû¿øæ§'2??/ïíëï—r2^fÅ„°èÇNJLJæýqÂûã*~ü׿üó“÷Ç]®¤”¢8)-#SSû_¿~^ÏÉHKgÿqö×TVäßÜ"åd.¯¨dÅ„°èÇNÊË/”²a™˜œ”·Qš™¡×óDòóòd/‚}ýEëÇ/&§Ê>,ùÓŸ™ŸžÈÖÖ–¼w¶ËÜ-åd®«o`Å„°èÇN*)ÕKÙ°\»v]ÞFiee…^Ï)7™d/‚Ï{ûˆÖÇ'\ÿµz?æ§ÛóÌ“OÉ~g[ÛÚ¥œÌmí¬˜ý8ÀI•UÕR6,##£ò6Jv»ýÉǧÝs{ÚZ[å½³¢•ãŽDEÇÊÞ¢ÆÇÅ1?Ýž¯õ5ÙïlCc“”“ÙÒ×ÏŠ aÑœ$ñˆ–¾>ÙK¥¯¾ü ížÛ322"ïmÝØØ° “}Âçæä0?Ýžw~ø#Ùï¬Ä¿Ý¼vý:+&„E?pÒà Uʆ¥½£CöRé§?y—vÏíYZZ’÷¶... Ø{ùøÊÿ–q}=óÓíùó‡Ê~gKõ)'óüümVL‹~उ‰I)–ÚºzÙK¥þú7Ú=÷æ¹gž•ý¶ÎÎÎ Ø;²»»+ïÈŒŽ2EÝž¨ˆHùÿ2 ¿@Ê™¼±±ÁŠ aÑœtWÚ·nõ£ì¥Ò…„Ú=÷æíüPöÛ:v劘ýøÒò²¼#³¾¾Îu{Š._–}Î'§\’ð/!üß‘¢8éàààœ—d%KN^¾ì¥RMu5íž{ãuî¼ì·µ§×"f?>33#ûà¼ñõ×™¥îMŸEþ£"£c$›Æ‘ѱ,—ý8Ày¡á‘’•,‰Ie/•&ÆÇi÷Ü›¬ÌLÙok}C£˜ýø°Ü'—:üñ÷`–º7«««òÞS»Ýîãç/Ù4ÎÈÊf­„ÈèÇÎKMK—¬d “½+´ÙlÏ?ûe >7¦ÛÜ­µ³ Ïr&m§üq\¼È,ucÞüÆ7e¿§›››RNã2S9k%DF?p^‰´Åâþþ¾ìÕÒ¿ú5Ÿ»òÔãOlmmÉ~OÓ3³ÄìÇË+*eœ®ÎN&ªã}ÞKö{zóÖ-‰ÍÃZ ‘Ñœ×ÖÞ!eÏrgaAþ×i“xÖmùÉÛï "*ZÌ~<;7Oþw76ž|ìq檻R¢ÓÉ~O‡††¥œÆ×®_g­„ÈèÇλ~㆔=ËØØÙ«¥þ¾>:>w%.6VözpppÞÛGÌ~<&6^„ßüìÝŸ2WÝ•YÎ\mni‘r¯®­±VBdôãç­­¯KÙ³´¶µ‰P§¾øü Ô|nIoOì7ô΂˜å¸#^>¾6›Mö!JMNa®º%ßùÖ›"ü£XW"Ù a¡„àèÇ. •¬j).)¡]ú该és=/>÷üÁÁìwsxxDØ~Ü‘öeºº%aá"<Á“.J6SÓÒY%!8úq€KÒ3¤;Û0ñÂEÚ¥ÚšZš>uTèPßÐ(r?><<"Â(}ëo0c]O_Ÿì·Òf³ùøHyÆ,«$G?pIm]½dU‹·¯Ÿoooo?÷̳”}.¦µ¥U„æ7'/_ä~Üñùa”âbc™±.æë_}M„[¹p÷®”Øjb•„àèÇ.¹rõª”mËíÛ·Eè˜Î}ü }Ÿ+y套÷÷÷E¸•a‘"÷ãYÙ9"ŒÒÕ«W™´.&&*Z„[9hµJ9ïÝ»Ç* ÁÑ\²¹¹)eÛÒÓÛ+BÇÔÛÓCßçJ¢£¢D¸kkk"—㎇ØívÆêÝw~̼u%“““"ÜÇòŠJÉfoPHK$ÄG?pUdt¬d…‹NŒ#:ÞzóÛT~Îå‰/=&HW(øáœÿ8¢óî]ƪ´¤„©ëtþ㽟 òà’òpÎܼ|ÖGˆ~àªRƒQ²Â%*:Vš©° €ÖϹüöƒ¹‰R¾Këtz-Æj{{ûå¿Âìu.uµµ"ÜÄÝÝÝóÞ>’MÝŽÎ.ÖGˆ~à*«uHʺp}}]ºð¥^¤øs"]‚ôã’’ÅïÇu¥zQ†+1‘ÙëDÞüÆ7m6›wp||BÚã"î°>B|ôãWmllHÙ¹ŒŒŒ R&]¸@÷wÖ¼ó£·¹}—ÖWü~\œ¿™XZ\úòÓÏ0‡Ïš¢Ë—¹ƒMMnÊâE ¸AL\‚dµ‹±Ì$HÙ´¶¶ö•ç_ þ;SZš[¹}W®^¿?Êò½{‚ ZLT4søLyãë¯ïíí rûRRÓ$›´…E¬ŒPúq€TU×HV»„GF ãRJ* àéóÓŸ¼+ν+3•+¥ïîédÐîÝ»÷ü³_f&Ÿ>zaŽÞÞÞ–róñ¾þVF(ý8À &§¦¤¬ îÞ§rzýµ¯Qž2ý}}âôã‘Ñ1JéÇsòòÅ·´ÔKÌäSæßýÞÁÁ 7nxdD²{ÞÛwss“•Š@?p»Ý"YùÒÞÑ)N]XQ^Nxš|ô׿‰s×—–”RŽ;âë(Nͺ»»ûÍ×ß`>Ÿ&æ®.qæ|©Þ ÙŒ½˜œÊ²¥ ¸Çå¢bÉÊ—ô̬C‘üügïQ>:Ï?ûåùùyqnYGg—‚úqGnŒ‹3z õ Lé/ÌŸ?ü“8·Ìn·‡„EH6]›[ZY¡ôã÷•ø÷Åéž&'&žyò) ÁG¤@¤B$>¨Ð-1Ë„À?ø!³úyñùĹ_ÓÓÓRN×»‹‹¬‰P úq€{øúJxba¯Pu!u>"ïýôßív»87kuuUYå¸#Á¡6›Mœ1\XXøÊó/0·?/ºâb¡P¦ò Éæj|âD(ý8Àm.ë$«`Ò22…ªŸl6Û{?ýwjÁó—Ÿ›êfµwt*®wäúB cuUÓû¡ùÃï~'Ôúûæ*ál®<ý8ÀmÆ®\‘¬‚9çå³±±!T usîæ‹Ï=O9x_L‚m âp19U‰ýx©Þ ÚHzŸ÷b†ß—×^yuiiI¨Û419)åD]Z^f5„‚ÐÜÆf³‡JÖÂttv‰V6Ô×?ñ¥Ç¨ãëí#Ú=ZZZRb9îH@Pðþþ¾Pƒ¹µµõƒï~y~œ§¢·§G´9¯7%›¥“SY ¡,ôãw’r—Û¸øÄCñÄÅÆÒå'o¿³»»+Ú ª©­Sh?îÈà U´ñœžš~‘Èÿ™ì¬,Ñnã3(åÉ=½ÖA( ý8Àæç祬 EÛØúh#ò?þþ…¯¿öµ;·ïxw‚CÕۧ¦¥ ø;¡ö¶¶§‚9ï}ÞKÀ»Óké“l~úøìîî²BYèÇnŸxAË;2;loo¿ûÎ5~&çÕ«W¼5££cÊ-Dz¸¸(àÀk¼ÿÕûï‹¶ûÍ‘¤äÉ&gQ±ŽŠC?p3swduŒ¯ÀÎÎŽ€ÔÒâÒ[o~[›Eá³O=mîê:RVN®Òûñªê1Çöâ…$Í–ã?~ûíõõuoÊí;w¤œœ“SS¬€Púq€›íííùIxJg§˜uáíÛ·¿ùúZ+ Ÿ~âÉæ¦&1ïÈÝÅE¥—ã?¥3Äñs„£"#5XŽÿ­ïÞ»wOÌ;Rª7Hw DB"Ë”ˆ~à~RžÒe³ÙÄ,§nÎÝ|óßÔNQøÌ“O5Ô׊Ê`,SA?îH—Ù,ì G†GhªÿÁ÷¾/æŽ7ëëëÞ¾~’MËîž^Ö>(ý8Àýî..žóò‘¬—± [.ܹóÝï¼¥…¢ð¹gžíhoöFlnnzûú«£ŒŠöwBŸmx}á‚FÊñwßùñÊÊŠ°7¢®¾A²9鼿¿ÏÚ%¢xDfVŽdÕÌ…¤äC­¬¬üügï©»(|套E¾ Mê(Ç2<2"òhæ<ùØãêžó¿ûà7›››ÂÞ‚½½½€ )·ÅgÕƒBÑ<¢ˆ[ +.VÁ^+ýõokkkâöÎÎŽ”» 岨AÑèÇ444,e]8::v¨&cÙË/~E‰-á“=ºµµ¥ˆqÞÜÜô Rk?o³Ùq#¦§¦ßÿù/ÚŒ¿úÒË••Jy¶465Kùòø;wXé hôãÏŠO”¬¬‰Š‰üм“–—>ùèce…?úþ±¿Ä±2S¹ZËñ£˜»{t;JKJ”õk¡'¾ô˜ŸïêÊŠRFxmmÍ×?P²éWPx™5JG?ð¬áá)ë¶ööCEèïÿÉÛï(âZ]q±RÞV>rçÎóÞ>êîǃC···tSVWWÃCCqPíÏÞýéð𰲞'ºR½dsï¼·ï; ¬qP:úq€Ç%\¸(Yeã´±±q¨4µ5µ?øî÷Äl ¿òü i©—6777ªé™Yê.ÇRQY¥¸[355õÉG?ùØãbÎyLJ±¾®Nq£:w󦔯HWÂê xÜøø„”­Þ`Ô½{÷òóò~öîOŸøÒc’µ„ß|ý¸˜Ø7n*\Ee•vÊñ£ôZ,оeŽlC}ýŸ?üðËO?#e3þã·ßÎÎÊZZ\Rôè­¯¯ûK6Ù‚BvvvX× ôã‰èeRÖ…õ ‡ªpëÖ­ü¼¼ß~ðÁ³O=í‰~ð‰/=öÎt!!Á:hULjÍÎÍóòÑZ?î¬Ðpî³¹¹YS]íuîü+/½ì¹·Åýþ/³³²fggÕ1çó /K9Ù:»Ì¬hP úq€D¶¶¶‚B$kp¼|üî,,ªÈöö¶ÙlNMNùÝo~ëâfÏ?ûå÷~úïQ‘õuu‹‹‹j¥ƒƒƒ¸„D­•ãGÉ/(TÓ­´Ûíc££ùyyýõoß|ý ÿâÅç_øÕûï']¸ÐÞÖ¶©À#|atlLÊi—pÁñMYÑ ôã阻{¤ìq/\Tú.+Þ€ÅÒÛk4’“.úûúþùÃñÞ{?üþÞzóÛo|ýõ×_ûÚ›ßøæ÷ßúî»?þÉï~ó[¯sçãbb‹/µ·µÍÍͪWMm6Ëñ£ Z­j½³›ÖAëÑ„÷>ïõÁ¯~ýÎä˜ê¯¾ôò _~î™'Ÿzî™g_záů}õ«?øÞ÷ßÿù/þò§?GGEæ8æüüü¼Z‡ecc#84LÊ9695ÅZ5¡Hçðð0>1IÊ*§º¦öš195¥ÁUNÆ/ hùÞ=f‚FØíöìÜ<)'XaQ1 T†~ ©¹¹›RÔéÈÄÄ$=šloo‡EDj¹?Jrê%›ÍÆ|Ðsw·Ä{Üoll°ŠAeèÇR+¯¨”²Ó ÜÚÚ¢JS½ÂËE”ãGiljf>¨Þ‚Ÿ¿”óª§×Âúõ¡Hmoo/,"JÊZ';'×n·S¨ñ"­FrÞÛg||‚Y¡b»»»±q RNªÔKé,^P%úq€ ®\½&qcØÐØD§¦V3³³^>¾Ôâ'ºººÊÜPë¶ã…—¥œNÞ¾þ‹‹‹¬\P%úq€3D}ÚÚÛ%žK­mí¬YP+úq€MFFG™-*°±±#ñäé2w³NAÝèÇr:88ˆO”¸ñ)Ò•Øívê6E³Z‡(¾O?ÿÙ¹9挢ííí]LN•xæääæ³HAõèÇ2»sgÁÇ/@âÞ§¦¶ŽÆM¹&&&½}ý(¾OŸ °E6ßW,›Í–—_ ñœ ÜÚÚb…‚êÑä×eî–¾1lim¥wS¢™ÙY_ÿ@*ï³&,"ree…ù£8v»½XW"ñl9ïí;11ÉÚ- !¿ Púư³ËLû¦,·æçýƒ)»KdtÌÚÚ³HYå¸ÁX&ýT©ohdU‚FЄ°»»'} dé룃SŠ………ÀàPjnW—°¹¹É\RŠÊªjé'IFf¶ã[³*A#èÇ¢XXXeߌîž^j8ñÍÏÏ…PŽ»£"OX__gF‰ÿæxEe•ôÓ#<"j{{›õÚA?È uH–ư½£ƒ>Nd³ssAl«â¶DÅIJ¹àå¸Þ`”~bxûúÏݼÉJM¡ˆE–ýihl¢•ÓäÔrzà¸Î¨Å¥%f—€l6[Q±N–YÑ××Ï­¡ˆåðð0#+[–n¨¢²Ên·SÏ etlÌÇÏŸ:Û  ¿uësL({{{9¹yr=Y€ AôãáìììÈrV§#yù{{{”t‚èìê:çåC‘í¹øú\¹r•™&ˆõõõĤ‹²Ì„ôÌ,Îä„6ÑD´¸¸"KO”t1eccƒªNöý—Ë+*é¯%È9/sw7SNvwÂ#£d™Ñ1q;;;¬;Ð&úq€ &§¦¼}åÙX#"*úöíÛvrÙÙÙ‘k‹ ͦ¼¢Òf³1÷äríúuÿÀ Yn}PHØòò2+4‹~ ®A«\ÛkøøX­CÔvÒ[XXˆŽ£°–>©iéëëëÌ@éÿT¢±©YÆÝììk ´Œ~ ´¦æy_ª=88 Â“Ìððˆ¯ Û^Œ ø[oÉ×Õ”šìï{t‡„EÌÌÎ2¥üS‰Ü¼|¹žoç½}GFGYe qôãÑŒe2Vä)©i+++yžvppPQYåöÛwµâÅO-ÿM5ÉOzÏÓÞËǯ³Ël·Û™“žvk~>*&VƇ›ãF³¾ôãÑ^–±Eò ¦ÎóèÉ„ñ‰¥(‚ÍfKÏÈ’wwf]IéÎÎÕžÛ‹Â.s·Ÿbí)ùº ÊñCËKŽþÄÇ6^½z)êv«««i™ò>ÍŒe&–àý8@ööö’.¦È[*EDEOPð¹ËÊÊJfvާïZpÀ_÷ÌÿSéýøHÙ˲Ìy½Á¸½½Í\u—þþ€ `yŸc…EŽ+aMŽÐc{{;>1IÞjÉ‘’RýÖÖMŸ‹¯wtv¹ñ(ÎG§!÷-E—ã¶žÿò¡\>$,|dt”Ië¢åååŒÌ,Ù_9¹ùŽO« pŒ~ $[[[±ñ‰²wLÁ¡aCCìHî¤Ûwî\LN•ò~ùú|²Þþÿ(·ï,ú†ìs>/¿`mmÙë›ÍÖÞÑáã ûMÌÊÎu\ ëpý8@a666¢câdošIKϸ}û6õßémmm™Ê+Î{ûȰOÈ¥+´ßéú_þ‰0á}ý›[Z÷÷÷™É§7>>'À¯ôIÏÈ:88`îC?Pžõõõ˜Øx*§s^>Æ2Óææ&=à¾BÛeî ‘ïNy-4=¡Ä~¼&ûû"Lõ“»ðŒ°ÝÊ[Z^ÎË/䮥edîïï³v¢(ÒÆÆF”o‘;âÜÒÚ¶··G'øÐ­ÆÇÆ®ˆð mvÂ(®_mûÿ¼½Ï ÕåRZÆôÌ Óû¡O§ŠÊ*o_?AnVfVoŽŸ‡~ HFS…`°8aphXGg»OœtõêµÄ¤‹âÜ£‰ªç”Õ§üTÀrüÄ~Ö97oÞdžŸÜA¨¦¶Î×?@œ{ÙÝÛÇ’|úq€"uš{.—æ…Õ†„Et™Í—Ün·_½v-9å’h}nbäï{ÿM)åø|ÃSç.Ç“›_077§ñf|ss³¾¡Ñ/ H¨[UXTâxTÎÌαjE?Pž‰É©ËÅ¥G)¸¬ ­1 ©­«___×ZK¸¿¿ßké‹O¶Ì4|U)ýxzÜ/?QB?~””KiccWìv»Öæüâ⢱Ìäãç/Ú‰ˆŠ=~N–L››[¬ÀƒèÇ ³±±Q¢/;î}),* °1ôòñ+)Õß¼uK -áÚÚZcSsph¸à5nxПzþ‡øåøµŠTŽ':6ÎÜݳ³³£…?’¸1>ž“›'êH8ùt¤¡±åõxý8@Iëšîë}Ž*òˆ¨aKÄ Ij- m6ÛÕ«×òò Î{û*¥Ãm-|SðrÜÞûo±aPb?~¿€’R½Zð\[[kjn‰øŸ˜ôàCґѱ+,"À}èÇJ2<2öÐÞç(1q‰‚—†Å%¥×®]?88PAKxk~¾¦¶NÌ7÷¿·:ÿ·Èý¸¥äëÊ-ÇO&6>¡¥µmyyY~ww×jÊÍËùWAç¼|’’S?ï Y\bX¾wu8‰~ KËËÅ:ý#úqG“RÎyù^—ê ׯßPbQ~ûöíºú†èØ8E÷¶å?¶ßëþŸ!UG?~œ “[ÛÚïÝ»§¸ ¿··7<<’_P(àã÷å¼·ï¥ôÌG?!+«jm6« pŒ~  v»½ªºîÑÕÏQRÒ2”²×‡`P~AaO¯euuUð7gÇÆ®Œeá‘Qê¨k½¼Ï/·|IÌ~¼1ï-••ã÷½Q^YU=>>!ø/‡îÞmïèÌÌÊööõWÄÀ:®33;ï4OÈÁ!àý8@‡†OSý%#+W)­ÖqââMåCCÂtå;;;ׯߨohLËÈôòñS_Q[ôž€åøFÇÿãëó‰Šûñãøúfçæµ´¶MÏÌìïï‹pÞæÝÅÅ^K_©Þ ¸ßùçäžòñX¤Ó/..±¦GèÇ pšUîKnþeÿ …V‡a‘…EÅmí“SSÛÛÛÒôƒûûûóóó½–>c™)áB’øÛÔ¸žÙºgDëÇ iïh¡¿ÿu~¿‹)©•UÕƒƒÖ»wïÚl6iæüúúúµk×›š[róòƒBB:z!a‘—ugz<~¶ËŠÝÎÊ|J?ßéwV¹/—u¡ájØ$4<"=#ÓXfjkï»róÖ­ ǰ¸ònø……ë×oôôöVU×äæåGÇÆ÷öÑZ-›ýPåøÝ¦ÇÏ{yi°°.KH,(¼\S[gé럸»¸¸··çô„·Ùlkkk³ssÃ##-­m¥zCJjZ€bv2ѱ N<Ùe8F?Ýðè˜sÐQbãUÙ!ž÷ö HHLJMKÏÍË/Ö•ËL•UUÕ55µuŽT×Ô:þ¹ÌT^RªÏ/(ÌÈÌJº˜ãã@{œQÓKâôãÙ‰¿àŽfüÉ#[pYç¹çaUuÝá!GuB£èÇ"jhjñ\Ä^+äÁœ;ï=ßð¤ôýxEæ|òyñòñ»˜’&ÁÃðêµë¬;Ð&úq€p¦gf%胎’‘•ãëH GIû¥ÄåørË—¼¼Ï3òäá'LJæäJó$,5˜vvvY} Aôã±ØìvSE•dý¸#ù…Åá‘Ñ”qÄ‘ë•/HÙ^ücNò× ^>±ñ‰…E%R> {-ý,@Ð úq€XÆ®\•²:Nrjš—ÅœÆþ{ï¿ISŽÏÕ=Í€“ã똞™#ý3°X§_]]c ‚Öв»»[j0ÉÒ;’›984‚zNã±”~Mš~<%æF›Ü—ÈhÏÅùè´´v° AkèDZô ÈU '1)å¼·/=fø—ýîÿáér|ÔôCMNÆÛ×?5-Söàí; ¬DÐúq€(Ö×7ŠuzÙë!Grò ƒBÂ(ì4›¦¼ïx´·÷þ[tè2Îä8á‘Ñù…E"<ýªkXŒ )ôãQtvu‹Pñ"9ñõýd³ãÿö\?n.~ƒA&ǯ§¤eõè›™™e=‚vЄ°²²Z$ÆËã÷½HN…§ÁÓÞñP9¾kþ_Ac„‰#Q1ù…Å¢=÷*ªjY• ôã!´¶uˆVçbJš·¯?]ž¦rÞËënóãžèÇks¾Çð¿€ ´ŒlazS¬JÐúq€ü—„퉎’_XK©§©ä&þÂíåøjÛÿëãsޱÕrÎyùÄÆ_(,*ù‰WV^e·ÛY› ôãù55· Þ%-#Û/ ˜‚O;™ªù²{ûñ’ÔŸ0ªZNPHXVN¾"w×oŒ³6A èÇ2ÿåñ“),*I¸p‘s;5’¤Èߺ¯¿Ýøä9FU«ññ HNMSгŽWÈ¡ôã™5·¶+¨3:JnþåðÈh*?-Äj|Õ]ýxFÜ/Omn¨›PpY§¸Ýñ V(¨ý8@NKKˊ댎“ž™íBý§îDÿé ç¿»^Žß¨|ÁÔ`BÂ"²sóúˆ3UTÙY§ nôã9µ´u(·?JRrª·¯?= ŠÓ~ù[.–ㇽÿþ{FRSñ º”ž©ôçÛÄÄëÔ~ ›••U¥—GG)¸¬‹O<çåC'¨Êø}´Ýù¿]éÇûJ¿Æ0j'^>~‰IÉ…E%*x¸UTÖð9Ô~ ›.s:úñ£ää†GÆPª2•™?tºßïþ¡a 5²ÕxL\B~a±šžl³³s¬VP1úq€<677‹uz5µHGÉÌÎ  §(TÛÁÞçïµþçúñ¦üï0€ZHxdLN^¡úžiµu,XP1úq€<úúÕW$çRzVGwª+…æD9¾Ùñûù~Âè©;Á¡™Ùy*~ Ý¹³Àšµ¢È`wwWWjTqt””Ké~Á´‡ªÉ\ÝÓgíÇËÒßfÜTœ °ôÌlÕ?Êš[ÚY¶ Vô㌎]Q}£tœ‹)—|ýiUÔ˜_Ÿ©_l~ì¼—ã¦ÎS[ƒB.¥giç9¶ººÆÊU¢HÍ~xXfªÔN¯äHaQIRr*-¹ 2Vþ•Ó÷ã¹~Έ©²OMËÐÔÌ‘K‹T‰~ µééY­UKÇ-ùÅ”K~A4ŒÊMtèÚ{ÿí4åøTÍ—.•%0845-S›/]©qwoõ êC?Zm}£6 ¦;®¤ù²/¹Rc.~ã ËñCËKŠú-c¥¦}Æ5µ›ÊC3:v•õ êC?Ôââ’Æ;¦ã¤¦e‡Ò<*¯* øÛ®ù>º2¾Â@©#¡á‘é™9<¯)3U²ŠAeèÇ’ê2÷P3LzfNHX$-¤²R—óÝG”ã¶žÿü'FIÑ9çå“•“Ï3êdfçn²ŠAeèÇÒÙÝÝÕ•è˜LVN^DTì9/zIEÄÇçÜZûÿûyýxûåo2DÊ—_L\BnþežK¦©¹… *C?Ε+×(˜‘¼‚¢Øø ^>þt”â§$õ'-Ç·»þw€ÿGŒãë˜p!¹à²ŽgÑ#²¶¾ÎZ5¡H§¼²švé SpYwáb*x ¿ÿ†×Æ'ìÇ«²~Àà(ñøÍäÔt>§Iÿ •µ jB?Èüí;TKgJZFvXD›®›Ìø÷ï+Çïµþ/ïóŒŒRrÞÛ72:63;§Íé£7˜l6+Tƒ~ ‘ŽN3Õ’ÉÍ/ŒOôöeÓ3^õüÉ~ürò¿3& ÚJ%¿°˜'Œ™žžeEƒjФ°»»[ÌÉœ.¤°¨$95=84œfS¨ÄGüþ°÷ߎÊñ›õO3 ÂïŠãu)=‹GŠ+ilneQƒjФpõÚ J%·$;· &.×ÉÅIékGýø¥Ø_3"¿0Ÿ˜”WPÄ3Äõéô›¬kPúq€ªjê(•Üû:yÊ¥ŒÐðHv'—=aAÙïþWÊ¿ÂPˆ¹ÃxDTLZF6 ÷fhx„u ê@?𸥥eê$%¯ (áB²`0M¨ŒiÌûNLè¡–”œZpYÇSÂ)+¯%ú2›ÍÆ¥£xVGW7E’Œ)¸¬KNM ¤('*ªÅý"¢b.¥g•ð—1Ó3³¬qP:úq€íïïëJ´H‚å)—ÒÃ#£½|ü(X‰b7Q‰»”žE-.HZÚ:Xæ tôãššš¡Bpë•K陑ÑqÞ¾þT®DüøúFÇ&¤gæðá-Å%†½½=V:(ý8ÀƒZÛ:¨DNFVnlüÿ JX"TÎyù…„%\¸˜[ÀçTäLLL±ÒAÑèÇž²·¿_\b ?RDrò /\Le›r"û*á‘ÑSÒò ‹øT*"Í-m,vP4úq€§LNNS)q÷•´Œì˜¸DÿÀ`êZ"Í«âÁaq IY¹|•·ÅŠN¿»»Ëzå¢xJKk;å‘¢“›ùbJZDT¬_5.qoü‚¢bâSÒ2ò ‹ù¬):㓬wP.úq€Gìíí±¹Šš’[pábjXD4§zWNÚŒˆŠ½˜’–›™Ï[¬" xÄÔô µ‘Š»ò¤äÔðÈÞ+'§yO<2:.9•N\½[¬”öö÷Yõ Pôãhï4Siä`ÏäÔ´¨˜ø€ s^>ÔÁÄ1 ‚BÂbâSÒ2ò 8fS™šžaÕƒBÑÜÏf³•”錴–‚˺´Œ¬øÄ¤ÐðH¶aÑÚÆ)á‘Ñ ’Ó3s ‹Jø,h-íf>(ý8ÀýnÞš§0"¹ù—S.eÄÆ'‡†{ùøQ"«)>~¡á‘q I—Ò³xIœ”èËl6k”ˆ~à~=½}F亼0%-#.áBhx$—+ñ ñ°ˆèøD qòðܺ5ÏÚ%¢¸ŸÑTI[Dü¢´ŒìĤ”¨˜¸àÐpöcíõð°ˆèØ„¤äÔŒ¬Ü‚Ë:f,ytzûúYû Dôã7[¾wªˆ8·KzfNÊ¥ŒôÌìØøÄ€À`zjÉŽÓL¸pñRzfÊ¥tÇ-àõpâDÊÊ«Xþ Dôã7½BUD\‰uhdhxÔ‘þkk[GEUÍå¢ÏNþŒO =çåC©í\Î{û‡†Ç'&efåèJôÕ5uí]ƒÖá£Ñnï03÷ˆ+YY]e„âÐܬ®¾‰žˆ8]©ñ¨®ý¼X‡F:»ºëš eåù…E—Ò3ãâCÂ"Ø¡åäî(aQñ‰IéY…E:SEUcS‹¹Çrü‹‡‡ÆÜmaúW2vå+ ‡~àN»»»E:==q:†²ŠG÷ãH_ÿ`[{gM]ƒÁh*,Òefç&§\ŠO ôõTÍ‹çŽÄ?0ø³<áBÊ¥ô으ËE:£©¢®¾±½£«Àêôè1ýˆ+ihla„âÐÜizf–’ˆ¸’Šª§ûñ/|ñ¼×ÒßÞÑÕÐÔRU]k(+/Ò•ææffå¤^JOLJŽKˆˆŠ  ÷ ööõ—¬Ow|#¿€€Àà°ÇÄÆ'&]L¹”–‘™›Wp¹XWj,+¯®©kjníìê¶ô xn|˜~Ä•ëôûûû¬ƒPúq€;u÷°Eq)5uêÈÀà¥oÀÜcéè4·µw¶´¶75·646×Õ7ÖÔÖWU×VVÕT8RY]^Qeª¨*+¯tÄñŽÿ§ãtüŸÿNUMã_vü'ŽÿÐñŸ;¾ˆãK9¾ ãËöõoÿ-Bt¥Ff q%7oÞb„²ÐÜ©ÌTICD\IcS«8}±Öb4U0‰+±ô°BYèÇn³º¶F=D\Lk{'=µ\©¨ªe×öGªe)„²ÐÜæÚõÔCÄÅt™{é©åJm}#3¸˜Í­-VC(ý8ÀmZÛ;醈«û3xìüIò…ijic319Åj¡¸Çá§Ÿ–LtCÄÅX‡Fè©åJ{‡™H\Œ¹»— B?p{÷V(†ˆ‹))5RR˘î “¸˜²ò*D(ý8À=®^cóqâjŒ¦JJjÓ?`e׳±¹É𥠏G›—SY]GI-oŠtzæ!q1““Ó¬‰P úq€{°ù8q=u M4Ôò†2qÃä=ÖD(ý8À VVW©„ˆëiim§¡–7åÕÌCâbÊ+«Y¡ôã7Ÿ¤"®§³«‡†ZÞÔÔ50‰ëÙÙÙae„"ÐÜÀÜÝKD\O¯e€†ZÞ46·2‰ë™»y‹•Š@?pƒòJöd nÈ u˜†ZÞ´ut1‰ëbe„"Ð\µ³³CD\OI©‘zZöt÷X˜ŠÄ-gí²8BèÇ®š»y‹2ˆ¸ž²òJêiÙÓ?`e*×S\b°Ûí¬ý8ÀUÖ¡Ê âzªjê©§EHq‰ÙH\ÏÒò2ë#ÄG?pUSKMq= M-tÓ"ÄPVÁl$®çúqÖGˆ~àªRƒ‰&ˆ¸ž¶ö.ºiRY]Ël$®ÇÜca}„øèÇ.Y__§"nIw…nZ„Ô743‰ë©¬®c‰„øèÇ.™šž¡"nIÿàÝ´iië`6×S¤Óïïï³JBpôã—ôZ©ˆë).1PL ’.s/’¸% wY%!8úq€K›[逈ë1š*(¦I_ÿ ’¸%×®ß`•„àèÇ.ápNâ–TÕÔQL‹“"ž9IÜr¨«$G?pÞææqKšZh¥Å‰¡¬‚9I\OMm %G?pÞÍ›·(€ˆ[ÒÖÑE+-N*«ë˜“Ä-ç ØívÖJˆŒ~à¼áÑ1 âž}zûh¥ÅI}c3s’¸%++«¬•ý8ÀyfÚâ– Z‡i¥ÅI[{'s’¸%ÓÓ³¬•ý8Ày•Õµ´?Äõ””©¤…Jw…iIÜëÐk%DF?p’Ýn/Öéiˆë1UTQI •Á!¦%qÏÑí,—ý8ÀI+««T?Ä-©­k¤’-%ú2f&q=å•Õ,—ý8ÀIÓ3³T?Ä-inm§-¦Šjf&q=E:ýÍÆŠ aÑœ44Äõ—(£L—¹—ÉIÜ’áÑ1VL‹~à$]©‘Þ‡¸ž²òJÊhÓ?`erwm ÄŠ aÑœ±µµEéCÜ’šÚÊh1ÃïÀˆ{>ãu ,šý8Àwî,Pú·¤¹¥&Z̘*ª˜ŸÄõ”èËX4!,úq€3ÆÇ')}ˆ»ö^ ‰3µõÌOâ–ììì°nBLôãg Z‡i|ˆ[Ò?`¥‰3-­ÌOâ–,--³nBLôãgttši|ˆëÑ•©¡…¹ÛÂ%nÉôÌ,ë&ÄD?pFM]q=¦Šjjha308Ä%nÉèØÖMˆ‰~àŒRƒ‰Æ‡¸žÚú&jh‘S¢/c–×ÓÝkaÝ„˜èÇg¶·¿OÝCÜ’Ö¶N:h‘SQUÃ,%®§±¹•¥b¢œÙÊÊ*uqÓ[¥}tÐ"§¡±…YJ\OyE5K'ÄD?8³[ó·©{ˆë)Òé­C#tÐ"§½ƒ“x‰R\b`鄘èÇg6>>IÝC\ÞXN-x,}LTâ–ììì°zB@ôã€3¡ë!®§ª¦ŽZü—˜«Äõ,/ßcõ„€èÇgfîî¥ë!®§©¹öYü”•W2W‰ë™»y‹Õ¢œYcs+]q=]æÚgñS[ßÈ\%®çúõqVOˆ~pf•Õµt=Äõ Ñ>‹ŸÖ¶Næ*q=Ö¡VOˆ~pf¥]q1ŽYDõ¬ˆôôö3]‰ëéî±°zB@ô〳±Ùl=ÄõTV×R=+"Ö¡‘"žK\LKk (D?8›ÍÍ-Šâz›Z©ž•£‰#:‰«©©m`…€èÇg³´´LÑC\Og‡s*&5u ÌXâbŒe, ý8àlnÞš§è!ÎÉ„œ)E:ý!+(ÄC?8›‰É)ŠÂáœÑIÈY³»·Ç ÑÐÎæÊÕk´<ÄÅTU×Q:++Å%æ-q1ë묡 ý8àl­Ã´<ÄÅ4µ´Ñ8++eåUÌ[âb–––YC!úqÀÙôôöÑòcîîuº¨í°Ê^Z‡­C#ê¾æûRWßļ%.æÖ­yÖPˆ†~p6m]´<ÄÅcú­ÃNµ–¾^K¿¼]s{§Yõ×|_øà×395Í ÑÐΦ¡©…–‡¸CY…‹UoCcËÀà°\Escs«ïƒË~Í}®½ÃnédêsõÚ ÖPˆ†~p6Õµõ´<Ä•ÔÔ5¸ØööõVTÕÊR4wtv74¶(ìš»œ¼æû¢+52{‰+c …hèÇgcªà˜>âRZÛ;]ïjëš‘¸hîí(5˜œÞF‰×|2UµÌ^âJú¬¬¡ ý8àlJ &ZâJz-n9mRWjlï0Ky¾¥ÞXÞÒÚ®©k¾o“f/q%ݽÖPˆ†~p6E:=-q:ºRƒ»Êßæ–öâCTç^VTÕè弿ãtu÷2‰+iï4³†B4ôã€3Ø?8 â!®¤¼²Æ]u­uh¤Ô`rd`pÈÓEsCãgÇÒvtv+ðšÍn|_WÒÜÒÆ2 ÑÐÎ`{{‡Š‡¸’ƦV7VÀíæ¿wîÕ-šÛ;?û.¦ò*Å]s™›®ù8FS%s˜8º†&–Qˆ†~pT<Ä•˜»{ÝÛØ–•Wõnž:ßÒÒ_\bølëä‹–¯ù(uõMÌaâtªjêYF!úqÀ¬¬®Rñ§S¤Ó[‡FÜÛØš»-G_¼­½ËCç[~ÖëU×iüšO¾üNˆ³Û+U³ŒB4ôã€3XZZ¦â!N§ÌTé‰×¥+«j_ü³s/{ûÜû•Ë+kŽj}Kÿ ×ìHÿ€•iLœŽ±¬‚e¢¡œÁ…»T<ĕ݇=Ñ[úŽÎtï¹—õ Íÿ¸ìú&®ù8Ž f&çR¢/c…hèÇgpkþ6q:Ý*mkë¾…©¢Ú½‰èJn쯕~ÍŽT×Ö3“‰Ó;,±ŒB4ôã€3˜»IÅCœŽçJ[ÇW>:‘Ò‘Z—_>>ßÒ‘¦–6å]s³§®Ù‘Ö¶Nf2q:öÃCVR…~p33³ô;ĹÊ*yµ5uR–ãŸsÍ‘¯¹š-ȉSÙ ‡`èÇgpõÚuúâDÎ~ì¤KímmÃÉï^¢/{ÄîçuõM'ÿåâƒ4[¥+úš[O+JÈúñ úqˆ…~pW¯Ò“3Çhª¸kܘۑ²ò*ëÐÈCηlïºïj›Z¥/ÇwÍ}ýƒLlâT?¾ÁJ ¡ÐÎà ý89{ê𥝛[w ééí;¹«ÉÑ[ÛƒÖaYúqÅ]óÉÝÏ 9eÖéÇ!úqÀÐ'Òe½´?x†dK[Ç¿žoyÿ¿pšƒ1¹æ£ÔÖ52· ý8”Ž~pì?NΚ"þ¡›„H–ÖŽ/ÆüÏs/ï;ßÒCY…Œå¸â®¹£ÓÌô&ì?¥£œÁµë7èwÈ™R^Q-cݬ7–ßw=Gç^ÖÖ?äÝçήÙûq]óÀàð};½ò…ÙÜÚb%…PèÇgpãÆý9SššÛdìš;:»¼¤Òö0‘½Çÿ—kî:í5›ä¾æ²òJf89S¶··YI!úqÀŒOÒï3¥§·_Þ×T^¥ˆëTâ574µ0ÃÉ™²³³ËJ ¡ÐÎ`brŠ~‡œ>%¥FÙ»æîÞ¾/¼ÎêšzqÊq]³¹ÇÂ$'gÊî.ý8ÄB?8ƒÉ©iúrúT× Ñ;WÕÔ=úѾ~«Pýøé®yP„ëÔ•™çäôÙßßg%…PèÇg03;G¿CNŸöŽ.:ܾþÁGœ$YßÐ,Z9® k®ª®cž“ÓÇf³±’B(ôã€3¸yóý9eŠtúÁ!Ajܺ†¦‡^¤®Ô8h°WÊ5·µw2ÕÉéÃ2 ÑÐÎàöí;ô;ä”)+¯§kzèN Í­íb–ãJ¹æþ+Sœ2ºË(DC?8ƒ…»‹T<ä”iljªnnji»ï õÆrëЈ°ýøç\³I´k6š*™íä4)5˜XF!úqÀ,/ߣâ!§LOo¿P5®uh¤Ô`ú—íÑ;Í"—ãJ¹æ†¦f;9MŒ¦J–Qˆ†~p+««T<ä4)Ñ— X7·wt‰¹ý‹¢¯¹»· ON“òÊ–Qˆ†~p›››T<ä4©­k³n>Þ ÄÜcQD?®ˆk.Ñ—1çɦº¶e¢¡œÁîî.9M:»zÄlrÍݽŽË«¬®SJ9~âšk…½Âšºæ<ùÂ44¶°ŒB4ôã€3°ÙíT<ä S\bùÜËÊêZKß ‚úñ^ó€°—×ÑÕÍ´'_˜–¶–Qˆ†~p6Å%ZòèTVÕ ~襲Êqñ¯Ùqy<È)þ¬¤›5¢¡œÞXNËC¶öNÅÐÄÕ7Ü«j™ùäÑééíc …hèÇgS^QMËC‘"~`pˆ¾Xkikïdò“GÇñd` …hèÇgS[×HËCSE5e±308T¤Ó3ÿÉ#2:v…5¢¡œMsK;-yDš[Û)‹µ\B™ã¬¡ ý8àl:»ºiyÈ#Ò×?HS¬Í4·òË3ò¨ÌÌα†B4ô〳±ô ÐòÏ‹ÑTIM¬Ùô XùGäöí;¬¡ ý8àl††GhyÈ祩¹šXË)+¯âS@>/ËË÷XC!úqÀÙ\½vƒ–‡°¹ yø+œO@>?››¬¡ ý8àl¦gfiy›«‡o±Ò?È|^XC!úqÀÙܾ³@ËCšÆæV bRfªä³@Œ®ÔÈ ÑÎfeu•¢‡°¹ ù¼4µ´ñY ¦¼²š¢œÍÎÎEasò¹[¬ Xù8S×ÐÄ ÑÎæðð°H§§ë!÷¥¹¥j˜ÅT^Å'‚Ü—¶Ž.Pˆ~pf†² ºr_ú¬ôÂä(-­|"È}±ô °zB@ôã€3«­k¤ë!'cª¨¢&Çâ¯LÈ}»Âê Ñά­½“®‡œLK[¥09™òÊ>äd&§¦Y=! úqÀ™YúèzÈqŠtúÁ!ar2m]|4ÈÉܾ³Àê ÑÎllì*]9Neu-u0¹/ƒÖáâŸrœµµ5VOˆ~pfSS3t=ä8fê`ò`ªkëùtãìïï³zB@ôã€3[X¸K×CŽ¢+5Z‡Fè‚Ƀé2÷ð!G)Õ—±tBLôã€3ÛÜܤî!G©©k¤&Ÿ—}ŸâHumK'ÄD?8³ÃÃÃ"žÆ‡8ÒÝc¡&Ÿ—úÆf>#Ä‘öŽ.–Nˆ‰~à SE5ÑËq<£ýìÀàêÁyœ-}|Lˆ#ýƒVÖMˆ‰~àŒÆæVÒÔÜöˆÎ´£«[ÞÒÖÒ7`éTý;Ú‚sYy%Ÿríú ÖMˆ‰~àŒî ÆS¤Ó÷XQ›š{,Ý2¾XÝØÔª…=Lç–¶>,äæ­yÖMˆ‰~àŒÑ±«4>OEUÍ–§Õµõ½–YzÛÊêÚG×÷jŠÈãZN‰¾ì”jm}cEe”¥m{§¹¤Ô(ÈÁ•’Eäq6w³“¦cª¨bÑ„°èÇÎ888 ôÑrNÿvöÀàPq‰A²·¹{-ýŽo×ÒÚ®©r\üqÖËùÔh6Í-m,šý8ÀIFS%½fcé<}—ÚØÜêøOÚ;ÍtÄ¥“ÞX®µr\üqnjiãS£ÝÇEÿ+&„E?pRcS+½6S^Q}¦:Õ:4Rª/+.1ôZú=ÚÛ–WÖü}ct³6ûq‘Çy`p¨H§ç³£Í\»~ƒ¢8ÉÒ7@ï£Õ“9Ï\@·µw:þÃRƒi`pÈSgE64;¾EYy•6Ëqñǹº†S:5šÛwX1!,úq€“®ß§÷ádÎÓÇXVñÙ»çž9C²½£ëèòº{,ZîÇEgs§tj4[ÛÛ¬˜ý8ÀIwîÒûh0 M-ÎU«]æžœíÙàæ3${z?;+Òñ•«ªë4^Ž >Ά¿w÷DS)Õ—±\Bdôã'íììPýh-E:}ÿ€ÕÅ}«iëèrïY‘G×fé¤yœ[Ú:øi-õ Í,—ý8Àyzc9í¦RéÚ Ú½–þ£C‹K =½î9CÒTQ}tmuõM4ã‚ó uXWjàs¤©ôôö±VBdôãç54µÐþh*ænWw÷®®møÇ® î8C²®¾éè«éJž;‘R‰vœëšøi*×®Ý`­„ÈèÇÎë¤ýÑNŒe®÷¶ýÖ£=¬1UT»ò¥ÚÚ»Ž¯­©¥Íõk´‹Pm»¥èvœ-ýƒGï¶äÎÂ]ÖJˆŒ~༉É)Úí¤µ­Ó-ýoCãýÙÓ›uôôö÷¿¥“uhÄ-×ÖÑÕ-óÖ(}–¾AusEU-Ÿ&ídooµ"£8ïÞ½ÚDWjtW=h.Ñ—å¶ö.§ÏŠtû)”æKG§l¹cd›ZÝøÕÄç.s(¤¬¼Š…‚£8Ïf·³U‚6ÒÐØâÆ"¸¥µãø+ÿý É>çΊül×S¥»wî®ïµ ÈÒWV×öXµ0Ά² >S𸣓öNJŽ~à’ªš:: Õ§H§wooëˆÞX~üõÏt†dm}ã¿ÚëÞ ³ô 8®Mú½Èë›qû—sœ[Û;ùXi!Ž{Í* ÁÑ\bîî¥R}ªkÜÞÛvtuŸü§YªÏÖÖ6«$G?pÉÒÒ2ºSî±7©»{ûîû^míŸ÷/÷•þëÇ5užë¬›?koÛ;Íž.ÇÎÀÔË=÷-çVÆùè—|¾TCYK$ÄG?p‰ÍfãˆNu§ËÜë¹êö¾ýë‹túîÏ9CÒTþ//A—ܾ%ú}€”êËßÅCïΧ¼²Æñãttv{ô»ˆ9Î÷mqN8œý8ÀU5µ 4AjÑTéÑÞ¶¯°è_¿Rª/ëà ÉÚºû‹ÔƦVO¿Ù}´÷™Î´<ó™œ ÍŸí ^^åéŸEÌq~𪈚22:ÆúñÑ\Õcé£ Rk$Ø`¤®¡é¾oz__ÜÚÖyß¿P¢/óÄQ–ÆXVá¹fÚ:ºŽ~œîž> ~1ǹª¦žO™Z3?›õ⣸jrrš&H•ñè¦Ø'÷¡Ö•ïûÖµÿÜóº»Çòà+Æ­mÒœœÙeî9úŽõ n>«³§·ÿh÷íªê:i~1ǹÇÒÏM•qL§½½=ÖGˆ~વõuÊ un,U ÝÔÒöÐïÞ?`-ù׳"ý“æªNîþÙÙ¡]î=“ó¨C´ôJö³ˆ9ÎUµ|ÖÔ—ÊêZG(ý8À ôÆrú •¥Ô`²HÓ~væßûâûÞ?5ü}{“ûÒÙÕ#e?Þké?z±º¸ÄÐÓëž³:MÕG?K]}“”?‹˜ãÜÝÃM*LOo+#~à­íôA*KKk‡”Õmû?7ã~tLÕR^ÕQªÿy­[ÎꬫÿÇ>àºR£çNþTÖ8—WVó‰SY&&§X¡ôã7»J¤¦”èË${yü¿Ã4Užâ¥Ô~éûñþëÑ^á®ÇmíÿUO7µ´Iÿ³ˆ9Îæî^>t*ËÚÚ:+#~à wéƒÔ”æ–vé{Û/,I«kêe)”ihl9¾ §7Eééí;î٥ܾFãlª¨âs§¦£}Y¡ôã7°ÛíºR#­/{î¨Æ"¾O£,ïË uøä –mí]NŸÉéöÓ>Õ1μB®¦´wt±,B)èÇîÑØÜJ+ÄËã.ÆÒ7pt惩oh–±Pv¤¥µãøbþ~VgŸsgr:b4UÊû³ˆ9Î'‡ˆ(:×®ß`M„RÐÜcxtŒVˆ—Ç]Om]ãƒW¥+5Z‡åí”ÑË/éLguÖÖÿËeîî•ýgpœy…\5YYYeM„RÐÜcáî]Z!5¼<ÞÚ.ooÛ?8t¼I·8Wu”Ž®î“Wuʳ:ÛÚ;OþWUµ"ü,bŽ3¯³ù8 1úq€{ØìvÝuQVd<4òd›ZÿµnâªzŒdm}ã£ÿýîÞ¾“;™8þ¹×2 ÈÏ"à8w÷Xø*=ml>E¡¸ [+=­í"ô¶Ö¡‘“‡a¶wš)”úîûÛÀÏ´þÁ¡Ò?ˆ#5uâü,bŽó#Î%ŠÈõë㬆Púq€ÛŒ]¹J7¤è]Ä©n[ÿ¹'IYy•8Wu”ªšº“ãV¤ÓwÎY¦òyÙ¼¸ÄÐ?`êgpœ{-ý|µµuVC(ý8Àm–ïÝ£RnÚ;ÌBU·†²ŠÏŽ²ì±ˆÖ÷õžÜ2å³}iôeýœÕùà˜M­¢ý,bŽsUM=ŸG…¦¬¼Š¥ÊB?p'½±œ†H‘­–©R´Þ¶ÓÜSY]'`¡ìH]CÓ}hú×÷¯[Û:ïûJôeƒÖaÇùÁß@¥¤§·uÊB?p§Î®n"%ÆÜÝ+`u;ðÀKÙâ\˜®ÔxßÖþsoñî˃õnk[§˜?‹˜ã\WßħR‰™™c„²ÐÜibrІHq©¨ª¶º6M-m9à´­³ÀZò¯gr:b(«`ÄÎZÙ—øl*+E:ýîî.ë ”…~àNÛÛ;lŒ ¸J«×ÒO!{ÖX‡FJ ¦óh;ïûÒÙÕÈ5Í­|<••º†&A(ý8ÀÍjêè‰Çý¢Šu.í]§áòŠjÆÊ]¿ "gtì + ‡~àfÃ#£ôDJIq‰¡_Ô=¾£©ò'òz¾“i;Ýo ˆ ¹wo…ŠC?p³¥¥ez"¥¤±¹•Ö•˜»{=ÂÕ5õŒ’+)+¯äsªˆM•,P"úq€û=t f"ZJ &ëÐ ¬‹©¨ª}ÄÞî}ýV†È•t÷Xø¨*"=½}¬}P"úq€ûQi)"ífêW×céø¼3ië›×S]SϧUüܼ5ÏÚ%¢¸ßÍ[ó´E‚‡C#ݘںÆGXWj´38®§ÀZ\bà3+rJôe6›µJD?p?›Ý^¢/£36E:}oßÅ«Û ÜÁ¡ ÜæÖvFÆ]ijnãc+r::Í,|P(úq€GttšéŒ„M]}•«{ÓØÔzr„õÆrövwcƒéR>¹Âfzf–U E?ðˆé™Y:#awB`ßO¸'ÿf‚½ÝÝž.s^1S\bØßßgÕƒBÑÑ"DWj<88`¥ƒ¢Ñ<Èf³•êËh‘dOm]#¥*QM¬C#¥ŸkÙÓÑÕÍ2¥£xVOo-’¼)5˜­Ã”ªDM1w÷òÑ–=·nͳÆAéèÇžµp÷.-’¼é2÷P§õ¥¶®‘O·¬GT²ÆAéèÇgª¨¦K’+5µ ©D•´³ËŠŒé°²ºAèÇ74Ö¡½±œÏ»Ä¹výëÔ~ …ééY%)£7–[‡F(O‰ÒÝÛW¤Óó©—,ºÃÞÞëÔ~ ›Ý®ç$=©R¤Ó÷ôöQ›í„¿P‘2æ5¨ý8@"}ýƒôJÒ¤©¹Â”h-eåU|ö¥É…V4¨ý8@"+««ôJÄTQ-BYÙ?`•ý­Ãªßd†q>N_ÿ ®ÔÀÀÓ©¨¬a9ƒšÐ¤ÓÐØB»äÙ}K"¦ŽôZú{û但öN³ê_šfœO¦­£‹‡€§sõÚuÖ2¨ ý8@:33œÒéá}»ºÅ©në›­Ãr}÷†Æ–Á!-ì+Â8ŸLMmÏþŽ“9¡2ôãÔcccãRJª§377ÇP;Í~xh4UÒ1y(u MBõ¶–¾ÁŠªZyÞ#nïÔÎ&ìŒóÉX‡F e< <”žÞ>2¨ ý8õ˜¿uëñÿó%O§£½ƒ¡vÅðÈ(“'Rfªp¯íÚú¦†Æ‰¿iwoŸÞX®úÇçÏßsf ¸„È=’{÷VXÅ 2ôãÔƒ~\¶··‹uzj&·ozÐ×?(`o;08¤+5H¹?µcJõem]Ú)Ç燾ØÎcÁí©ohf ƒúÐPúq¥è4÷Ð4¹{Ûña«Û¦æ¶âC¯¥_šog*¯2š*5UŽ3Î߈¼®‘'ƒ{3;ËöbP!úqêA?®Ë÷îÑ4¹1Òo¬qÖý K &½Ñ40èñ3$kÿ^‰v™{5Ø3Χ¸1¦ŠêÃÃCÖ/¨ý8õ W†¦ú&·¤¼²Füê¶­½K‚KmmûlKŠª –ãŒóCÓ×?¨+5ò”pK®]»ÁÊU¢ ôã róÖ<}“ë)5˜‡QݽÉ[ßÐ쩳"{,E:½#’m0¢ÕqîSÖ8w™{‹8ðÀšƒƒV.¨ý8õ W–ŠªZZ'W"åfÓn©).»½ÃýgHöXKôeŽ/^S× årœq~øÎì-m<.\Ì u˜5 jE?€ÂÜ»wÏb±].ºx!)*"2Ðßÿü'ŸÈë •[@ôãÊ211EëäJùÖÕŸö Is·åäþÑÕ5õn¿¶Úú¦†Æ‰¤»·Oo,wûÉ«"³¼q µ©¢šgË£v_cçqhý8ÂY__ò±ÇéÇÀ ôãÊupp`àòÏÉé;Meå¾v²í/È÷8+ò¨Âëëtû… éJ îÚüTg`•êË\9ES‰ã,ÂVø†² ž0MgW7 4‚~áX,e•ãôãÄA?®hW¯Ý “z0Uª{o÷8=½}÷½¯úè<¬C#FSåÉÿ¤¾¡ÙC×ÖÔÜ&åi¨¦ò*ǦÁq–=ýÖRÎ~ØQÀëë¬JÐúq„SQ^A?ΡW4»ÝÎv÷E®°%KUMýÉŸ·ÔPöˆ3$«kÿå_Ö•=w¦uh¤Ô`ÒMƒ?«³¶®Ññãtu÷jpœEH_ÿàÉ—å‰#–¾–$hý8Â1èõôãàúq¥›šž¡™:yš¢Ûw£°š<¹Éõg?uyÕCÿÍæ–öûÆÇñ¿xø´Ì.Çw)¯¬ñèwimëüû/Bj4;Î"¤×2 +5òÌ9J‰¾lgg‡õÚA?€pjªkèÇÀ9ôã*P]Û@?åHYy•º_Ú=N}Có}?{m}ãgEöÞWïê& ~yp´Íˆçvéîùì LGz-2Œs(ã,Èn?TäGe%‚¦Ð œááaúqpý¸nâí;ôSe¦J”ãGg$>ØK¶¶už|÷ùÁAšÃ3»Ì½ÿøvfOì|}´­GM]ƒÆÇYt÷ô—4þð1”U°ASèÇÎÞÞÞ³O=M?ÎT«ôãªÐÜÚ®å~Êhª”`Ïk¡òàžE:}wOß?ÏŠ¬xðåzÉ®­¢ªæè¸B÷žÕéø¹?ÅÑWî°2΂ÄÜcÑxE~c|‚5ZC?HÍf³ ôú˜¨è¿ý寿ÿÍoñÞÏüö;Ä‘_½ÿK»ÝÎ$þôŸÒý¸f­­­ÿë&+LJ´VJ†ùà&ÈýÖû–<йÇ"áÎÔýG[Ž8®Ð·¦¦îû54µ0ÎbUäÝÚ­È«këY@?Àsúû½Î{ñ¹ç·m‚”iinaªæ®.úq ײ¾þA¶UÑTÚ;Ì='ðÁÿ±²ºVâk;î²Ë+«Ýò[Ú:Ž@‰ï¸Èã,ÒF+­Èï,Üeõý8OýÿÓ~ûÁoè¾O“ßÿöwLàÈo~ýý8ÐkÖÞÞžÞXΜšJYyåŽR‘NoéøÂú¬Çmi]C“ë;xŸÙÚÖÁ8 Z‘kï¸ÎöŽ®ÿ¿½óp‹*ËÖ~ÿ;“§gºÉYĈ‚‚¢¢¢¢ ¢ "DÁˆŠŠŠ‚D$ˆ" ((HÎ9JΡ€ªÊo÷0×Ä¢jŸS'¼¿g=÷™ÛÝTZk}ö~ÏÞkáÑ ,ŸL |+iK-niiAÚ@hoo775ƒ>ÐÇEKuM­x”©øÄ×"ǧêZüÔQÉ)i¹¶Ôwé߯ácægõ{rþ§büÌeû’“/‰<êEìð°Ï}@Ÿéé:ZK z/È‚®]Gæ0EqQ_^°A@ÔQ|ûö½®…°-1)¹¨¸r$±W¯“çqÔó±š*Î^XTò]×&—ñ%'OÕžœÿïö§ÏÙð3Ç-7¯ z®â3³’Ò2¹U¶]ŽŽH!f080î¼g¯®ôq€>.ÆÇÇcºy»tç´‚ÂâïÍ0§ÛÇŒO¿¶OŸ³U{½‘•;}¿ökಟ9h…E% ¯^£-'ÐÇ ÀÏ×2·:V[[‹,`N EKKKÆÇ±11áOžÞ ¹tCó=í}}ú8/¨oh˜ rK{ÿa†Ç^Æ%räÚâ_/TMΛ֓s*úyù…ð3ﬨ¸ôõ›EÑ1q##£xÊèãFX½b%4nuì¼²F©®ª‚>€>}œ/¼Oÿ(A**úå笨ó ‘3š"~Îú‘kû’“7ãUÇü•RÈo™Qš#%õ=üÌ_K}—.$}¼ºûÒ€>3ŒŒŒ@àVÓŒ GGñèã}üD"yñ¿B_wkÆÆçä@dü©}Ìø4½y §®-é7GÇÆÍÓ«óurÊŒ·#…E%ð3ß“S}ƒSÒÞãáôq˜âëׯ¸Õ·èÑÈ% ôq0EUU ßÕ¨ø„¤y„TØ {—8µA;'7ŸS–—_8C%‘ó¿|Ÿž1#ÞÈ€Ÿ`YÙ9QÑ/ù}åEìàО,@€)¡n«oöv‘K@èãà;oSßñWzœZT\ aQyûœõ…øíMr*¯-%õýŒø&§¤Í–PgÈè1/ã9˜\ö3—-/¿0ö?¯xjeå•x¦}éíéºMÅJŠ‹‘N@èã`ŠÁÁ!>îÙŒŒŠyŸž=Q{•”œ_PÄÁ +,*™Š>~š.žÎþ2>eÁÏB2’‰IÉ|Ç“SÒ ž)@€YŒô  n«oÞ'O!—€>ÐÇÁwxWeånœÙèÆ©zIÎ^ÛìÚ)‘Q1Ù_òþ¯'çÌÅq?¨Á?óÝÒÞ}àÝ 48ˆÊ*@€yö»¸@ÝVßô´uN@èãà;iïy£F½Œ•—_Q¨Ú}tlüŒˆ¿ˆ‰Ë/(šÑÀsʲ¾äÂiBµOŸ³yt´¥ªªÏ À±11P·©ØãGN@èãà;###³uIÚ›·iØ–+lËÈÌš÷1q³ÿá«×Ép—àË‘¿ŒÅýq)íýKŒ-3_ u[}³¶Z‹tú8@Ó©ohä²õüEìÇÌÏP Å`q ¯”)@Ÿ›W_‰áHÁ›·i\š¢cãGFFðú8ì÷2ê6ËÊÊB:} ƒé|ÎúÂM*>1 b¨x,+;W™^ˆp”xìcæç9ÏpÁš¿¶àÙôqØæäñãP·Õ·#K@èã`:“““‰¯ÞpM~Ê+(”Éd--­P Åc¯’’ç?LPPX /‰ÄJJË»ºº%ÉÛÔw\rróñàú8𙵻rƒÀ­¦éh-éêêB:} ƒéôöõ=Ëí)6.±¥µíûµuw÷””–C.ƒåæDFÅÌSî.‰•–U M …¢¨¸tžÄ`Ù’Þ¼•Édxj}Í —Ëok/Ö‚Ì­ŽÝ½}¹ôq€>fPYYÍíécÆ'©T:ãÚ†‡‡ËÊ+!ŠÁ’SÒ~Pî9=ZEb•U5c³îî.s‰Š~900ˆçÐÇÐ0õuu^Ç>^]] éPðVPX<çQ’œpެ¾¡ñGËÕI™,'7_³úx}}#žÌôqXe`` 1!ÁÇû´Ã–­Æ†P½dïÒÒBPú8@@!šÚ¡™þ!cddtþË“ËåMÍoiï?ÌH—q‰p‹¬­½ã§ÃT{Gg|âk S_ròð˜`~  I†‡‡»ººš››kjjª5ÍzkŠ‹Æ Ö6©))t¢.{‘3Pú8@@'ýQÑ/Ù”œb^&Ô7,`KfgW7Ê‘ ÛŠŠK£câ¦'Éç¬/p‹à Ž“ÁGÉA`rr27¿€åŠäÉoÓär9žÌôqÀÔÕÖÑ]4†?yJ>vó&{ŠŸ©õû¢ææf Z@’±±±·ÉÉL[OO\Í5êY“œ>}Γ.ô ‡%’òŠ*Hжé¥~^½CX°ò’¬Æ¢°ÖÂ+_‹nU³êóªêÚÙ]~JwOOÒ›Ö^ãI$#x:ðS þàÒ… e2=mÁÿ¼E‰¡+»^¹‚`@ èãFn^ÓzS«×ÊÔRø“““µu Ð4l/ãIžDFÅääæÃ +ãŸú ö(rÿö-÷OßMšgÚZXRRÂô·77·Èå ÕÆ¹BQV^Áô‘’„ê Vˆ èã€oR©ÔÄЈ¢Lvòø‰©O£ûÉæ&¦ãããT€>@`(ŠÙ5 iYTôË’²rJmí¨µ"Tûœõ…dË›äT¸‚Q«.~=‘§;]ŸncùÕÅIŒÕT)ïéíUHFí-\YU‡J}ð-!>žî.ï‚üüïNwg:±Ä„„ *@ <ÆÇÇèÕù!ãÓð°„âu k¤ÖJ]ñóÎÂSý»ú vwž®.~ ¥•º½JJÎ/(‚Ý9>8>eм4Ýc ¦JÍèØÅ¡ µµ-1‰þ•ý%”ú8àÛ‡m5²õÖ6Ó?¼¡¡Aë÷E?Ÿ\-B $ƒƒ3Ú$ªcIoÞ¶3S£`rr²®¾‘5I±±(Tšg6[F(p,/ɆäJ·Q'œÀ¨ l_ÿ?ûKKÑ5š5U¾¶0Ñë’|fEeutl<­Q+%õ=zr°   z¬ºš‰ÎœÓÙëäD÷+ª«ª8(ÜþÐǔֶöçQ1jjL/ãkjë £—ÚÙÙÅt­•²’œ‚óȈÒ<ÓŠ’L¨®0^X}ñ3åÄñÿJäÍEwhÔT©èëïgt(J¥¹yÏ_Ī9pÅ'&©Ð=‘}±sÞ?€‰ÎœÓIMI¡«Ÿó;‹À >ÐǘšÚ:•¦1q¥e“2;—:22ZYUØXYü^šgþSq$5 - a0õm8ßv!úø…Vj‹_ªóµuõ¬5ÁÎüœ©êë½˜ØøÁÁAŒÿ,èãˆ:sŸý-2™l¹Å2Šßb¨§/‘H>Ôú8aSXT¢B΂Âb2IfùRårEKKâøDž¾’2b{¡´WÇ­¢ä÷ܿ,H'6žgXV’«Âו”–wvu³?võöõ¥Ì\ððõ"¶³³ #?*}Qû’îÎîü¼¼9¿èvp0Ý/z‰ð &ÐÇžOY_””–ž¿ˆÍÍ+ÓàÕQlÚY^’=žg¬¼†(Ïû·j" Æšµ^\¨8>eýN oÅY«Ù¡»»'íý%G°È¨˜Æ¦fŒù¨ôqDͶ­ÌuæœNgg§öb-Šße·Áá@M >g­s€ò@ ¤Ré«×o§ËIqñ¯**ª&&&8{Í““²Æ¦¯ªÉŽ5Åñ*Ôhž²Š’Ðma,XSQÈDžÞŒôÍ_ÞXöã’AŸTǧ¬µðÒüÛÆÛ8³m|N†††rró£¢_N;äæa@M  RFGG YV«­­ÖRüFÝ%Úýýý%*}€xOLzö<úmê»Æ¦f9‡%°é .´"yIIéhþ •įE7 ݘ¶®Âó$áPþ–ò’¬ÙU[«¦>>™§]ZRôƒjã\Ü6>'ccÒÒ²Š—ñ¯È€–ù)Kñµ>€H‰‰a§3çtž>~B÷K†=@(PèãDÅððpOo/ï.[&“}mi-)-WRyl)º¦Ž€ØSpè§›Ó» ½†ò7Iò×õìj)º^Z’Á¦¼µ^ùiNäéW¿žµåüžšúø¶_™½m¼£³K¡à™Î,W(ÈÈ W@€ÐÇ)[í7³Ó™s:ƒƒƒºz¿×jõ„•>ð‰DRU]óSå±´¤`2OGõP’oó£¯-~9’¿v®=¹K‹B!û”±Êâ4EÞ?”IEYÞ¢šâDŠï~þ¯ ¹ñôϬ«kJ¥a9ÐÇ#lv朎÷iº_ý)3@5 m…ç©èãŽ_[Ze2ÆÀÐÇ###Fú,wæœŽÃæ-¿]{±VGG € @xÊààPeUõ ñ±¹è.õ°¤¤xš8^4œo§ôÞs“’4?„ÁŠÿ8…à­BBöìþ¿Âå©dø·ü~“b|ú8¢#:êû9§÷2Žîß¼…° ÐÇþòG¹•ŽÎéåV$ùÖTÔò’œiÕ0v.èo[Š®BƒU'wz çÛIò× 8¶^(/ùôÓ¿Ë·T-'›‹î’?ÿZDG'Ö—ˆ1ðè㈎͛ì)*b6ë¬zR©ÔÔȘâ5XZ,Ãá8Tú8ÀwÆÇ'šš¾þ_åŠ?SÒÇÿ[΢½Ðo¡+Í3ƒv,l+))î)p›]&E‘û·Þ‚ƒå%Y?úCò¯TNÑɼ%e%9E¨éã G0z¾}qA½ü÷“ÇU¸Œ+—.Ó½Œ”·o\ ôq@HFFzËüiI‡¥%Å%eµÅ1ßrÿªÂŸ×Ç/Dl-âÌ/q|þz;“yÚõÅÏæüÛ†¢'ê¤eO[mq,5}¼Hãà;ÐÇ~¾¾t;s(Ý™s:MZ¿/¢x%{œ\ ôq@8”¯£¢*rÿ6Õ“s<ÏXµOè,ôž_c-/ùÜ^è7š¿Z‘÷)Eu `WmñK¨ÏÜ·ÞWe:µ¶]›ý·í…çÔÌL’$Ôôqb£•6S@@DH$C=}ŠrØ //•/ÆyÏ^ŠW¢õû¢ÆÆF„€}]ßòèW™ÌÓúOÅ*‚$ßzžÝâ…¾ò¼_çúÿt+Ía«+~®t”?Ï®D¯œ¶>ŸõìýAò¨d]O0r¦€>€ˆˆzþœnU“¼Ü<•/&-5•îÅ\¾x !`A@BO4-Ñp,yEɇ©Ýª™<ïßsª«%™#ùkæÿÛÞ‚ý¡9k#ùk’ i,z0ýχò·¨¸Aªj‡Ï¹JÅȘú8"b“­f;sNG&“­XfIñzLŒ¥R)¢ €ò@B£'-Ñp°À¡¿ÀIÍ©(ù0CZ­*N™ÈÓWæo[Š‚ DsÐjŠãþ¦ä×êâ×ß?A’oCãýÍ2júxùŒ€)  JJJ¸Ð™s:!wîÒ½¤¸—q4Ê}¥Ki‰†½…û¿åþEÍ©+~1]Z­,~?‘§§äßÊò~/+ù=škÖSpX%9Û¢´¤hêFóWªŸŸò¼QÓÇóÿñ훃àôq¦‘Éd%%%¯“’^DE= {p3èÆå‹—üýý|}}¼½Oxyyz;yüùßäŸNþííààG’ÿþUâ+ò·åeeíííp)PßÓ>\èÌ9îîn­%¯ÊaËV¨LUeÕ§ÌL2ð†?yz÷ö«þgÏ‘Á™ Ñ^Ç<ÉpMþ÷Ù3~—.\¸~õZðÍ[d Ž‹›¥ÛZ[ÇÆÆøõ“¡‚XvQlZ(É_¯þ‡4…|×UËJr¥yæ ìðy z4×Lå~­]…Ç(êãtíó»ç¹99 ###H3ÐÇ©ÑÞÞž•ý"*êj`àa7÷͛엙/Õú}Ýí±¦FÆmmÉç^¾þñÇææf…Bÿƒù‘H$ºzéÌ9#GéÞ#åå7PæŽ(*,"#öå‹—¹ºÚ¬³ÖÓÖ¡’däß¹ÃÑÇÛû^Hțׯ«««e2gý}à)CCC¥¥¥¯_Ý ¾vÍ™¢b¨Nåñ9k¤¨Puz2O»¤¤’4w¬¢ä£ùð×êâWÿ)_¾š;):eNvÿú>•5ÔÓ'‹—½ÎþþáOžfdd´´´ÈåØ`€(€>®"2™¬¢¼<.öå¥ v;î460¤«ñ-È tõ¶Úoöñö~üèQ~^ï60ˆ|ÉΜÓÉùò…î…ùùú"Ü`ÎA»¤¸˜ÌtOxy­]cEýåå<¦¯£ë°e«ÿÙsÑ/¢««ª8åèã_hjlLLH¼¸ÏÙÅÂÌ|ú#8àÐ?9%Žk-¼<¥«’ÿ¡Ú'ÔE@•æŽ5=P'†ó7η¥”¨¡•ðž»ýéL~»ƒC€¿lLLEE—7¾ÀMººº²³²#ÂÃÉóËë˜çnÇëÖXYZ,314ÒÑZBæä.371]½b¥Ý[×ýÈÂ9ôÞý7¯ß444°yÐÇÀèè( êíà`§]»énÅ¥kÚ‹µ6ÚÚúùú&&$tvt p€@îtæœÁzkº¯‹†‡‡q@P(åeeäáºg·wmòì÷8rôyd$ËÏû9>p–‰‰‰ü¼¼»w]÷036™çüÐïï´äByÞ¿)éãWþ(;^’.ÏûUµOè.ô€*Íë(<£fJ4=(päZ}•€Cÿ\èbsŸ³ Y_A+`N†††222‚oÞrÚ¹ËÄÐH&ùsç={oÝ OC¦ï8èã?§¶¶öa؃½NNt %³fÖVkýÏž{—–6::ŠhŠ“â¢"ºIõøÑ#Š—þô)ÝË{ ‹™±±±´ÔTo復fŸW,³ ð÷ÏÎÊÖÔôú8À5ª««§–Ÿú:ºJ>‚“ƒÿÆ5Ù±¥èÚ*«lVùFóWA•æŽõ¸ª™#ùVªuøœ»O6ϹzìêlÌ:ìæÓÝÝ ˆ¹\^TX|óÖæ›:«mbhäéáñ*ñC53 ÿü¼¼óþ«–¯à£&>§é.Ñ>à²/2âY[[›ž”J¥H'ÍrúÔ)®uæœÎðð0ݽ½¬mtB†šÔ”òÈäòùžy6•ûžöùøáËB¹Èõq…B1::*á3Âî%Enj ÏÁHHÑœDЂLr¾|¹p~¥årÁ9ÿÊ5}¼©è^}q¤z•^þ‰äܱÁõ³¢·àµ.²t>'Èëêϵ~_´Õ~óý{---ÍøÇ×Ü?£@®0##Ãûä)²žemíl¤o@ÖÎÐÇ™¥¤¤$ðò•Ë,#‹Ï9|;nÛñ¬¯¯ vvtwÝ`ÕòºK´§Šv­^±ò«kø“§xf°ÌÐÐò{O”±ãž^Ô/ÒÏ×—›åÑ/(*,:{ÆOÍ£[1 3󫬕^•>ÞÝÝ÷2îBÀy§]»--–èê±Y†žÑg:™#’®óž½—/^Š‹ëêêâ£xúñÃ‡à›·Ü²ZµšÜÎÚ‹µ„1é"?„üœuk¬»¹“˜ññ£àEsòÉÏ$?–üdòÃ…ÍÙ ™ñêCVøþgÏ™«wè­2šN9fégjúx}ñ³ÑüUj~Heñ{Ó1Iþzõ³b$ߊk/r®{þƒî£ÁaËÖð'O{zz0¸qÞžžÄ„D2[&sæË,y±H~“ÌYæåæ‘58›²ølÛhkû:)‰V]èãÿep`€Œb¶6ë,‹Ï¹fÛïâò69™Ö[)²&'Ó»ù Ñ,Y´øˆ»{sS²Ž"ÂÃé¦Mnn.õ‹¬¨¨ {‘žÇzÁ3<ÆXÇ232÷ìåãZ€ƒúøàÀÀ“Çm֮㎗֮±Š~­¾ª }ü7ö^Ç<§¶9‹Ö,—ZÜ ºÑÖÚªŽ'?¤§+¿“8<üéS¤ ÐUÉ8ÈÐunÛê@wFÒ‹—öÂ¥©±1àÜ9C=}1ˆ/¡÷î AWY}pÚµ[´w§»Z9|f‹Ìb¯^åis*Ï©ëW¯ ¦µ×ÄÄÄ¥‹…ºOú8 KFFÆÁ®tĿpl[îŸ%ù64д܇0Í}œÎÖoEÞ?h¥Yê jûɽÿdôIaµzÍðƒƒƒú4BWW—Ë^gþÎ48¥W”—Ÿ:qRO[‡›¾²Y».ý}:ôqUP(©))ÛÄ,‹ÏÞÙ}ØÍ]µ">dÐWáuYJaÈfúõ—;sN'!>žî¥†Þ»ÅEEn‘ÁJTƒ³‘¾Aàå+ÐÇDII‰fOüqÁL òó9x/K$’ÝŽ;1õrvÚ#€öédÙ/Î5ôq° ¤RiÔóç6묙ÈF*ûÇÛiìÆýoïÄíkÑMÓœ©¯bC%7äyÿ¦•fÉÁkS;iwÛþ‹…ç…¡žþå‹—ÚÛÛ1² YéX˜™óz¦Á}üSæðyá±=»HÜ¡+Ëää$™šX­^ƒUÙÌqûŽwiiÊ»41!Aåïzôð!næ8uâ$ݯt;sNg||œ®’µzÅJ…B ¹99|y*3·Û4ÀߟîÁëã555Ɔx O-É***8u;OLLìrtDhþ+‘ïÙËë]äR©ÔaËVqÆú8P’‘‘‘°ÐPFeš’çôñêXj›Ð'ò )i¹aZHý9éI{Ÿ}ÿTóCVšþ›Íɼ÷ÉS¬õ9m­­KÕëë}œš’²ÑÖ–w«æà›·&''¡Ï‡\.‹}¹zÅJ,Æ”,uÿþÝ»Ÿzµ³£C ‚änmm-†o&ä~gÎé\ ¤›Ã?|@€ÂÂB1—Ș]¹˜<ï% ôñù;NÅÓ¸­Y¹jdd„;7õ¥‹”éFnjþÑgÏø‰6pÐÇ2Ï£‡aÌŒM˜ÎÆŒPu M4¿úsO*µýãò¼_)éãA¦9b}.\ÓÇIÆjýö»:ïuÓÿ¬‘&p>ÞÞjÖ¶ó£P(è–o¡>ž‘‘±y“=]g·~CyYôñ¹y›œŒÕ²*ý—7oùüéÓ<Ž=ãã«æW¸¹ÄÎáOžr¿3çtš››é6Í8xÀiÀkêëëI1Ï6sS³§Ÿ¨¿çT¨úøíà`$É º~#Ñ!Y'¶IÊìàr¥øy(+-Uç[èã@yäryô‹èåËØÉÆèËWSj¼tø\S?ÿPí‹‚!LsÄ: OÑ «ìËŸ)}Οþ8û¾þ_rUûÊ~zðWM=At—h_8ŽY ûR3 èãùù;w8 c‚½ ¢»¢ÐÇ+**„] šË^çºÚºÙ¾P¿³YY577c§Îzk^tæœÎ~º%õÛÚÚ |„Œ-þþ¢mܧü[q5KK REe•9«¬Ð:v &žÇŽÙvñü>ŽÕ‡ÝÜÅ5èãàGäææn²µc3=ÔR·þ²uí¿(6ç¤õQ E!LsľQ+¿“Eí Š‰Þo$ÿŸ_Tñý¹q4ÞdèaØÁ4ëª"}¼««ë„——ðÄÌÞÞ^èãßúúúü|}±M‰Ö9 ÿå§£_DSùð{!!Äé’Ÿ—ǗΜÓIÿžîeß B2ðŽQQ,œD†iý¾ÈÇÛ»¿¿úøw^'%!1æ´„øxGgddï½~t(„w=3EMèã`6½==žèd`¯–ºí¶ý×Ã;~¥V\%÷o´>ª¶ø%„iŽX]q4OXšü¡ë/ùM…µŠœ?­Yúo.oꘙ±Éô‘Yäú¸úU¿„j§NœÔxt232ˆYuu5¿Fl꯴¡¾§©ü†:¿Mf«¨0¾ù£Ä„ßrPý,/ùaš#VV’KñdÀH&ÏùÞ]Ónå¿% ûÛœÇåÔ3å°›{ww7Rõyúø ôñ…ÎÏ­­Ö ¾˜áóÈH1êã­--Î{öb¡Åœ9íÜÕØØH\½få*Zç0ŽSd``@O[‡bĽŽy²vñ÷BBè¦ë›×oÜgbb"øæ-l,U³àþB‹ Rßîà€d˜Ó6o²×xt?z„@æiu?äôq<¾Áƒni6!óÃÿªZÂå&(Œ×Žq®þ¸"ïP¥9eãy†´‚[õ*Ÿ3•½SvhÛ¯²…¼%r±ÿ•kC#Õ¶¼€é©<ÓúøÀÀ€ð ªÌcgÏøÍSÎH€úxø“§ºzXe1mzÚ:w‚oSlÊÔÙÑ¡œ³DnNkßÓÓCW$Ýí¸)Áqª«ªìÖoÀ¸ª¾-55{—–&r}|Ý+dœ¶b™¥Æ£s#(ø‘=‹ˆÀúú8à#ùyy+-—kþæõ 3söV¾>ÞÛÛK·¹ŒM+--ÅPN ›µë(†ÆÚj-ë4ÊÝÛêëêœåу‡ºK´1R4ï“§~ôÔƒ>.øãª—Þ[j¡ñèÜ º@üÈŸ<å×è}!à<ôq<ÄADx8GN¿™èý&ù¸0m1ìÌß¿ÿùStôñÎj%8ú öB’攵]¥Üúø¿¤ÝU·NýdöŸ´fݶþúÓB+oÿliü—Ÿ/&†F™Wó‘#îîÐÇç§««Kãgž4{¦vÎS×ÂÑÇ?úD&©X\ñײ>ÆPN…Üœº¡yôð!Ë?!/—rsQ²†Gbpþþ~¼ÔdªÏÏ:ëúúzqêãŽÛw æ4»õ4!U„¤n‰ üÃoCÇ£\ÌÈd2®u¼õý»òÚâ›[›®-Þ8NAŸÈú“ô3µýãm…!IsÊê+3iw4óOëþ¥n†¼ùóœ7ºeÿ.yþÃú-íɶ]ùo^.¤91];vÔCãÑÉúüø‘•””ðk'k6èãxš‹©Têºÿ×rÒ@û·¶7JmßN½ý·%¿ÿÏß^t§ÐŸó}È_)Ö¯)N€$­q«¬ªiiiíïhkk߹ñ>þ/´â«§õÛ;õ¶ÏÓ`Së·ßOîýgEôÿ\íxÖŸbÿfnð¿T¼ªÊ* ¹¢ÝAWïïï?ìæŽ)÷÷“­]×ó¿»Èy¯îsÆöC!Xúû÷Ê© ytKU°Ù™s:‘Ïè&XLt4Òƒ;<ŒD+Nv,ðò•yšRóú â>§½ˆŠÒxtFGGQOéG/´æ¹U¹IoOÈ·§@-‰ÄqÛvn¦å–µÿÍœ·éeΟýûìª^N¿ªÛN3çOgöÿ“ZññÏ»w/$=ý#j–­¤´¼ªº¶¥µ­¿`bbr*çsss§ê/èŒÂüfiò›å¿'²Tÿ„ð€¿ÿôŽXmþo¿úìûç-¿šèýÆÇg®^bB"^婪¬‚>>›éé(¹1Ã6ÚÚN¯JÊo}¼ººÚjõú8øÎ£鯅ÍΜ3†zút‹L!=¸€L&Ãö^–m¯“Óðð°xôñ±±1œœsq588È…Ñ=ä$ó=íÃÇ!ý€Ë>èã@l§ Ç yí°ùWï»?ÿ¨…ëÖ_çü+Çõê»øxÿ¯'÷RÓÇSïümê–š™ïqÚsñÂ¥ØØ—yù…Я™°²òʆ†¦ŽÎ®¡ááÙ/kß<ý¾¯ÅnÕ¿i…xÍÒ«)¸{îþU>}{ÊÙ3~˜lÏin® ïõñ÷ïÞ‘Å }LgÝ+ºç¹4ø[¨â¼;º.ÈýVÎ{öb¸cßȱ½½]$ú8!øÖ-}†] äÊæ†ª*ÔÄ›aÚ‹µ”iÀA ¡Q199É‹™Œ©þoÏý½'õÏßÛFüåÜÁê,þáþYsƒßÔÑ:e_þd·êß—SÓÇ}÷ÿsÎÑrƒÍú#îG®_ŠOÌ/(‚´­š•–UÔÕ7´µwô÷H¥ã?Jøñññ“ÇÖBu,+k—ý¡ëiýÖ ÊÊsþįJ)êÛ.GG>å#¯_AŸ¢¤¸»Šç·aaüÖÇ##ž-Y´˜/î652^om³ÝÁaŸ³‹Ç‘£>Þ§üý/_¼D«7ƒnß¼EþoÐõëW®\8ï{ÚÇë˜çÁ®ŽÛ¶Û¬³^jjFæÐÇ2|ÉΦö;s2§ÜMu8A’hÎŽ»õx1éh-Y½b¥Ã–­\öðò 8wŽŒØ×¯^#Ã51ò?ÈÿKþ!ùW®û±zí+=mŽÿ(K‹e"ÑÇÇÆÆÖR}YÈw[µb%§z0Ù‚ò?u®\áïØÎµþ„ÐÇ£uRT_G×ÞÆbÿ®µÎ»w¸r#ó?_ßK.E(Y{NÍjÈÿ ›ÀËWÈút(Cõ-ä1WþØî}ûÔ?¨(§“ÙJIŸd™LVÙn‡Ü®\¾òìÙóŒÌϾT2¥²ªº±±¹½£³`>A|:ƒƒƒ»g»=à· «ÌþÛ$sóš/¼ÊJö£™ÅÇÉlÜjõ2ßçìâéáqöŒßôl'ÿ—üoòOÈ?'ÿ–ü7ä¿$ÿ=÷çðÓ\pcc#†bepÚµúxXh(ŠšþÔÈ 0u[ñR'pnºÕØÀp»ƒÃÉã'îÞ¾ó*ñUaaaKK‹T*Uó÷Êd²®®®ŠŠŠééáádúâvðµÕZéæÐÇÕçØQŠÑTgÎéЭíH )/ BšW­XÉÍÁgí«£‡yó뤤ò²²m:”‡Ü/d &r;8˜L»mÖYsm“¬¡ž~FF†ôqBee%ÝM¼.l]ZZÊ©a||œÌ—š)Û¹Ãqbb‚¿ÃûØØ˜Ãæ-ÐÇxö€›©Hžw›7ÙŸðò"3¸Ø—9_¾y—D"Yð/¬wWMèì}÷gSý?äì{§éèã¡Uy_šÃ–­žÇ<¯^‹ˆˆLOÿXT\*Êâ5Ímí½}}##£r¹|¡¹ÐÖÚºÞÚfî|Óùmø#…(OåÌ”u]°æ~ûì*ßÓ>a¡¡i©©dB«Îº•ü-ùò9äÓü|}·ïàr¡3c“ü¼< È?ß›ÕÙ¹b™¥hõq²¤uvÚן©ñUó!WWþéã2™ì¸§§ªyîur ¾y‹ ¦---,{crr²¶¶61!áòÅKŽÛ¶óëÍ'ôqêôööÒ}7¨©ÎœÓ¡~0êéã'Hö!3Ω–>1Ý%Ú»w’5ä§ÌÌæ~øÐÐPvVö½§]»9Ò“P{±¹­Ä rsr ‘“‰Š¦ÚHÌÏàÀÀ{{ˆã[¶’‚ïƒ<HÅ)‘C………ÜÙœdl`¸ÏÙ%øíEho¬jBçÑÿ-M«y#źÒd¶ÎjmYYYWwOk[{CcSuumiY…Pê†WT×Ô56ý!…÷ôô K¥ãÿWJWu*ÊËçïãvæïê˜Ñ$öɹ…|f©Å·o æîw…BÑÐÐð:)éBÀù¶¶\ÛòB²š\†åŸÒØØ¸jù êã™™œZ}OO]2õ%·Uô‹h²Lþúõ+™OÖkkmMŸ~'øövöë…Ô×ÕñIŸœœ<ìæÎ…s÷N»v? {PR\Ì© E…EÂÂö:9qDˆ>Î&a¡¡t#’óå ²ÚÜÔŒâ²Y»©Â2•••æ&¦\dVZ.ð÷ÿ”™©‘BR©ôó§OäÈehÖd¶#}|jZ¼y“xEØÍ7ÕÕÖqvp 7…ß™3¢­EN~øÙ3~ãããÂêI4/]¼(’’€ÐÇEÈðððš•«4~|¿‹Ë£‡ÉÌJ¡`F”K¾åÿº`­³éTsssô‹hOc÷Ïü®¾8Þ“úçy꤫fåee³´ÙèèØÐÐPoo_GgWKKëÒyM]EeUIi9§ …WTV×ÔÖ566·´¶uvvõõõ “Ù,CZDA~¾±áüþ´0úm4S­(·½ùó켨´D>˜ÁòkàÔ”TooîhŽd>ÿ"* ƒóOéïï?xÀU<ú8®rmvM–cAׯç|ù¢ü‰ÉÞžž°ÐÐÕ,@¾u‹7ú8Y?Ø·_³ÇÖN?žš’¢ÊQ5ÖMKMõ>yÊÌØú¸H ÛuA³9§C½žÒ—ìld kTTTh|Zf¾4ðò•Ù¥·5é–òrrIóoÌaZ˜‹|)}üÛv%&$lÞ¸I\Êø&û„øx¦Ú·ƒÇ‘£¢ªH~ì÷óÅPW[wêÄI}]èã@`ø9£©4314òñ>ýþÝ;–Þî7žX˜ÐY¹ñ›âÿ늎êëãw½ÿAÝ rerùøøøÈèèÐÐpÿ@oo_WwwGGgk[{ó×–¦¦¯ Muõ µuõÕ5µ•U5•ÕåUå•eå•¥eÿ±òÿXEY9±Jò¯È@þ³ªªšê2XÖ×Õ7’!õõkk[[{GgWWwù¢þ¡ááÑѱ‰‰ ꢨIÖçÏJ–Q³–Îçs—йyüòœŸýù× Ž%%%W5¾ßej>¯Ùna<âó§OÎ{öòqOÆ‚ôñ¶ÖV‡-[¹sñ[ìí†= W¥æ:ÎjÕj®ÖÙi?ôq2سÛISGÑÝz›œ¬~q “É>~øàuÌ“Ë%´ S™ÊPîÌù€+ÏÚ––º‡k<ŽE°CCCÝíÿ ÝUqÈÕ5ããGö—JB.,##ƒ\¤¦ÚM“Û\ úøô„ òÔÇÛ{«ýf#}á'¿‹ü:òÉ/åÝp180ð*ñÕ…€óN»v/55ãQv%‡#Ë¥davéÂ…¤W¯™,ëÄ$IZjêÕÀÀ.ûV,³ð¦rèã"¡°°}U…Ü8d†ð.-íþÒæoùJ랥–ß&ûþçÏû’ÔÇG3ÿd¦ÿ›ÆõqqBÖãÊ—l5Ôù­;õÏ*úÁ™¿ÏÙ,êÔ‰“_B¿ÿðo›}­¬¢<ùyy¾§}4.³Ü½}©«$ÑQ/þ(1¿m»©‘1/äråõñ>pdw,ñ-™ñÖÖÖRT5Ÿ<~Ìô½f³Îšúøäää>göƒji±ìvp0¹…„1 G„‡Û­ß}\=|„nY¨¾¾>îü:ºgGt´–tww#g˜¦­­MSQŒ ¯\a¿'„Ê´¶´ Öˆb{òø ñèã³gZCCCíJCffÔeÓÂÂÂv5 ³”ïeûÆØØXOOO»z0ÑÑ=ëógå/€ütâR©´···„ܽ },{»,o¿Ø¡ÁEhÛ ¥$βß&ºf½+TStöïLxúøOyÿîÝBÏryîþUå@»nýuÆÙe2Ëúÿïß:ü‘cÓÿªÜêÛÀ;®ùM"‘<Œ´µY¯A!%øæ-$°jŒŒŒtwwSŸiPÜ硌>®P(HpAîßhkÇPñÀæ¦&Çí;˜»øõÖ6\×Çårù÷Ã,Õf5 ª W›ßþ³ÑØyÏ^èãB‚,¿éžL÷ô8Æ©øñúÉFÖÆHF!³[›µëØFÌMÍBîÜåi³»ÁÁÁ»·ï˜ów+%¿ôq˜œœ¤þÖçòÅK1˜‹õãÏ.{áX¾ó<2ú8Pžä7ÉìMcLLCïÝÑôV|«Ýó}³Æñ›lx®Õ»ä[žê{ŠÓÿl¢÷ôqHª50{w÷o*ZúùOÚ¿}ïS–šúÊpí߆>Êü6ÞÊq’ënÇšš„“¡iÌØÔÇûúúœöh|xÀe_vãelÉ@AÖË 3uÚ¹‹ëú¸·7›AµµYÿ69™õ:Õ¤¤¸X#»ò¡3y ¯3çŒqng†•–Ë9[sCŒïÜáÈ~‹ò°å»÷$IÐõëܯˆ%N}œþä)]§‘X÷÷÷cÜ`‚¸Ø—Ô“<7'Ž…>}\<)(;ïûɳàvp0”ñï¿|â[ƒÇÜâf¡Ö·®Çóým©…Êúø÷2äaèãóPŸ¯òÌÓLÿ·Î· ~#ò6øoäo·;8° ¨±Laa¡Ó®Ý™‡“9*’YlúxAAÁr‹eš-‚ïvðPEy9›îý”™inbJý·øûsZgâTì«©,µx%6É,77—åƒÐÇ™`ÍÊU±ncèýûÈ7¾àuÌ“å¾çüÎööôɇ]]]>Þ§y×CF úøØØØRÚUõoݸ‰qƒUk5ÝHípØÇB‡>.*Òß§³ðô<âîÎÑ’žƒ™ßj÷~+\ô‡¦™ÿo•vß:B¿ÉòWM§TÇKŸÿE‹1'CÿÄ3Ɔjµà[û¯±O ‹µ¯û rs Ø«YŸ?oµß̾Rù26)-}üñ£GšíoÈÕ•eeü;mmmmméþœÔ”îêãäÞf­ùIà•+Øu¨òòETNôC¯TO™™BíÌ9ÞÞ^ÕýýÈö»¸ y˜€úi†ùmÛVM=•Y ¸¨h“­ôqÁ'¹‰¡‘D"ÁèA—Ô”T&*ép,ôqè㢂éCëæ¦fïÒÒø°b”. #â@š âøØ§?­_ñïÅÐÇYטÈ8¦¾{ݶÿ:™­l¬»3–‰á¼>!>.Îr©›Sq­%Ÿ?}Bbk¦õñ±±1O .úìí6jüH%Y=íw¡VÃÌØd||œ£úxnn.;oB¶Úo®¬¬Ä ÜÓÓãqä(ôq>rØÍ]À9§sÜÓ‹î ö¯_¿"èòñÆÊÍ6#}ƒ¨çÏïR™Lö ,LO[ú8w ³1C#Tä8›·Ð‘Ý[xU@JÒÚÒÂèA.g§=Â쯘üV¨½P}Üwÿ?¢@ŸÍðð0Å®’Gwþ:ž¥D¬óÿñm¬V¾tÖb÷ÿX‹qQonj236aaÛøÝÛwPƒx:¯“’4¸‘ú¸ tuu »3çt òóé¦ÜµÀ«H!Š´µ¶²6€ìÙíD¾N<¾­¯«cÿ&ôñy¸tƒ®ë–ššI¥R #´ÈÎʦžÞI¯^Á±Âú8P’aaÌ=1oݸ)ä-´m×$Ž¿¾ËxÛèã³u¥½NNt¼Ëö_=©óÖ"Ïûó·Þ8z»¬´ŒMAs¥åò®®.$¹aNÏÈÈДdG~T€¿?ϼ†Ü½«æO[»Æjbbâõq²>¤^Gf¶­^±²  ÷íl:;:Øo¬}\eî…„»3ç èN,ÌMͦÆA >“““[¶²slðÉãÇâ\ÆÜ ºÁÚö|èãóÓßßO½‡jøStU¢õ’V«VcG…`€>”d‹½=C3™ø8¡K„²ÁoEJo!¯wqÝú8›ø9ÃHÉ ƒß¯ÿMž3W  ~ûÖ—(Z‡“™|ð­[¬m$ßj¿y||y®)ÒÇï…„hj1h·~CQagþ26Våm£FúU•USŸÃ9}Ü÷´Ó¡uÙë<44„›öGà  öûÂA_( …bÕŠ•‚ïÌ9¨çÏéfÝ«Dì¤Càå+,ŒkV®*--³Ÿ³²²,ÌÌ¡sË/ÑõÞŠe–“““LÔ§¬´Œzn¿ˆŠ‚côq  ýýýL¬†t—hgfdŠÂƒ}‰J‰ã ÇÈêóÛvZP¬$ }ü§Z³RÚªG^ü{Sâ_9ÿÙ3^búí«ÿ·‰Nx¾¨°ˆ,gØ™–ûxŸ†Ã5u}|dd䈻»FÖwzÚ:÷BBd2Ç}þ)3S…VÃ3ÊÜÒÇããâ˜Žî¥ °HRSRô  s™ŒŒ º!xö€ã?™<覥ãöH$õÉÎÊfáÚÁ®ÃÃÃðvww÷‡mÐÇ5Ngg'ݦÁÄb¢£‘áêsôðºq±\jMXBú8Pn)”ÂDyOò±"rb[ÐOêPwüOï ©TJfæÐÇ™¦¢¼œÆ6\öõ÷õ~Sà´îÿ ‘HŽe©³btÔ 8\#ÐÕÇ7XÛhdq·m«ùv¾¸½¾®nA޲´X–Ÿ—7ý8¤744P?­<£\NdÄ3Ü«ÊSSS³jù èãœÅíà!º‡=9Û™s:çÎÑM<40Q“ÁÁÁå˘®_½&’N÷Ê011qêÄIèãçì?º\»Æ ¯ðÕ„L⩟<åþËc°  e ~HˆØ£EçÇîÈo¿Ï!Ž×ìü66Ç ¼««‹¡srÐÇÿ;oXMõüñœ¦õû¢à›·0uŸ2µ`¡ÖŠîm‘½Õç¢>ÞÞ&†Fì/ët´–„ܹ˻…‰T*½réòOk­ÿà¼ÀàààŒ?çŠ>Nü¾m«£ÑÛº dŽÂB9xèã*ÐÙÑA÷™êéáÁ‹^SSC7ñÎC:©ÃÉãÇ™žØ¡ Î&Öì‚>>––êk›×IIÈmu ^¦ÏÔÈxddŽÐÇ2Po]èæzP¤®œìûÖú­Öù[¥Ý·š]ßZ¯~­šç?Ïúü™‰é ôñ)X(Ñ`¤oð.- ®þ)_²³ÍML™‡Õê5˜Æ°Ç[Fý¼DÒÛï%¹ùHssóµÀ«³÷’èêísvyôðaoOÏœÈ}œz›ÁsÄRè·;8@çwoß¡ëò„æËo§ÛB–Ìá0iP™ô÷éŒ Æ†ÙYÙðóHzõJåV$ÐÇ©@ýýÝú Hl•éìè ~Gᬀ èã@ÌMÍ(Ž$&†F?Zƒ¹ž­' 3A\ìK¦§‚+–YVWUÁÕʪxMMk×X1_ˆÜ®fþêãäʯ_½61!²H£££ ýýý?ÝÏ }œŒ¡Ì­ðÉ'§¿OÇ-ª‰„ÑÝýÐÇ ¹±WZ.UgÎé¼NJBã5ŽŒ ŒVVYf¾”<ÌàçùÉÊÊb¹WôñéÔÕÖQßæöþÝ;$¶j\¹t™n, tõÈdŽÐÇ23ºƒÉ½xUy:xÙ }üëׯLÏí6Øvvt ™f°°1åí[¸šMxª¯´\ž››+æÀi^—Ëå›7nb(ÀÚ‹µ¦ú½5fºÐ ôqåùNyÓ.¿Š«NNNÒ-M¸ÉÖI¥—.\`n@Xn±ŒGÍ@4Kaa¡ ݺ¡Ó‚úQe‡-[‘Õ*080`¨§O¹©ûÅ‹p¬ð€>~JCCݣ̳‹œ‚ùñ>y ú8E …ã¶íŒNw;î”H$H]J¥®û03c“aa>êãî‡ÜÈ\ZäÓ¼>ù,’¹£!'E:;:Ý+ }\y¹ºŠ°3çtnÑM¿¢Â"äÕ‚¨¬¬d®«ÌJËåÍÍÍp²ò”k¤s ôqYrS÷dVV²z¡P/;FŽØ'H ƒŸ’›“Cq09ìæ—.”ü¼<èã|‘\ˆíwq‘J¥È[•‘ÉdGŽ2#¾4üÒÇõutÉÔQû¦q}¼¯¯ÏÔȘ¡0_¾x ¦,Á””è.ц>®Y:::è¸||X¶µµÑuÂÉã'Z ‚nø… [ZZàá…RVZ¦ñ]ä¢íƒ½ÏÙ…®'vîBJ/ˆ±±1êM®|¼Oñ‚ú8ø)?| 8˜D„‡Ã¥ E¡P,¥Z^ÌúxWW£SÄÃnî“““HZõsþô©SŒNÔ3>~„ŸÙGúøF[ÛºÚ:„l ëãÌ n‘!¦™áA×,·ƒƒEÛ™s:t7Ñëië ˆþ<‘ò$¿Ifîô_mm-<¬¹99$“¡³õmnÄ ‘ÒÊþä)õöDM(ñ$P ƒŸ’òö-fÚçØQèãT`tcò}û!ŽÓB¡Pxód.X«W¬ƒŸY€/úxÀ¹sãããˆ×w4©WTTPïj5e6ë¬QýŠ9\ö:C×r¹œn•~uæœNFFÝ |ôð!LÈC”Ì®˜ õôKŠ‹áaux—–Æ\Ýèãó@ýD…ëþÈg%‘Édt{Vó8rŽ*ÐÇÁO!3ŠãIUe\ªa¡¡£P]]-N7~ÊÌdnâ·g·ʪPŸÒ¸rc.dA×®ÃÉ,À}}ÜØÀðm2:5ÎD“ú8õóÈß#ÝÐЀÐ2GKK‹¾Ž.ôq@¼$æÎœÓQ(V«VStÅZÞ¾*`™'31t“iDúût¸W}žED@gŸÌ ÊëO­ßUVV"Ÿ•!îeõd®¨¨€c… ôq Ä|;ã‰ÆIMI¡qÖî“Ëåvë704ëÛj¿ytt‰J‰‰‰ÝŽ;™«4ÝÞÞ'3 ÇõñÍ›ì¿~ýŠ0ÍFcúxΗ/ ;5%qešëW¯A×öí§Û|¬··—¿ÞxF¹'ÞçÏȱù‘H$æTËAbÿ>œ÷€>Î>d®‰-ÌÁf5õ>cðª€>~Ê—ìlŠCJfF&\ªÕÕÕ£ N%7:ês•:zzz¥ 144´ÞÚ†¡Øðò‚‡™†Ëúx€¿ÿÄÄb4'ÓǶle"ØgÏø!¨,088h¨§}œeÚZ[éµÇŽò»u?ÝjˇÝÜ‘fós÷ö&†nôÁ£‹\.wÞ³ú8ˤ¦¤R?Tóp?%-5•z&çåæÁ±ú8ø)åee4w<ÀUJ¥´j±Z.µ¡GFFÈÅÄ|ÏÄШ¾ ý˜¥µ¥ÅÂÌœ‰ð‘ÛJÌåøÙ›ú8jªüÍèãÔ!OÙkÀb 硳̭7éú<;‹÷ý‚N?AÑ!Ú‹µ:;;‘i?B"‘˜Sº7ÙÚaè¦N__ߊe–ÐÇYfíÍ>§OB2ÏÃæ-t}¾Ãa¼*l ež¡8’ÂlÖ®CK•ahSË’E‹322œ,Ÿ—§£µ„‰ :ïÙ ÷2 õñÍ7577#4ó£}|Ïn'êñ&cŠ»±Iee%ôq6‘Éd–T;s £ÜvQaÝ<¼Œdû¡÷î3ñ»¹© ¾e‚‚ü|†fÕÐÇDbB"õ¹M[[’ùGЭ0e?|€c… ôq  FúGòîîn¸T/_A?Õ`hS ±;Á·‘™¬þä)C“ö’âb¸—9¸¦ûxŸG\~Šôñ²Ò2&B~3èÂÉ2t»#BŸêGÈ„… Ã3›lí(ºe¹Å2™L†|›T*]Ê@åñÄ„ø–9BîÜ…>Î&dô ûdü£Jà¹sÈäA½ŽÝ[xUð@Ê@·¥Dà•+p© äçåQ©&!Â7Ía¡¡LÌô¸ìCZ²Œ§Ç1&Byð€+|ËÜÑÇõ´u¢_D#"J¢}ÜãÈQ&*« Æ<ûœ=ã}œ5ö»¸ 3çœPï<ƒ¿sò"*ŠúЂïL#“ɶÚo†>Îë;…ÌkÑkNèž²×IIp¬à>”áœßYº#ySc#¼ªmmÕt¾û!7±9M*•2Qºš|¦`Ö”!1!îã°eëää$»P>~ø æ– šš±9-2âÓ¼ééHH››Ë„†æéq ¾e.èãûœ]‹Á¶>~'ø6õÀ÷ôB 53RçäPŒcY)^mýAAèÌ9çýè6õÆŸ0ÑT9>.Žåï“úø<Îcccæ&¦hÌ(d¡N×ÖËP©O$@Jbk³žúƒòÒÅ‹pìB‘J¥N»v«àm?__º+;‹~çj 3óÁÁA¤¢]µb%õÈbzÉÔÇMŒÑj^eXÕÇÕ<5§ÝºqQÔ ´Žík/ÖBGÝ‘š’‚Μ?Åiç.Š.236!sqäÞmmmtŸñäÓª««áX¾?¡Ïýê“Ý‘‘8vŠÎŽ­%t=üèáC8V$@J|ëÏJïÓh¿PÈ´ÜÏ×wASÍà›·Äé«ÃnîhÎɼ²›–Z`8bMéãvë7|ýúþWVõñ#î‡é†ßÜÔL"‘ Š„ÜTBio·ÎüÎ{ö¢3çOI~“LwxIˆGîMq÷öœ/ªí½‚>®ÃÃÃtû"{öŽâÊ¥ËxýTú8P’šš†—N;wuwwÃà %åí[k«µÊ”z/,,§‹:;;µkQOWäG8xÀ•úp”š’ÇRG#ú¸Ç‘££££p¾:°§“…‡¾Ž.6ûŒ ç©„òœßY8sNš››µ~_„Μ?…zšm[~SX­ZMѱzÚ:ð*û”––ÒL ÏOÐõëtÝK†8œk!  êéÓõmð­[p¬x€>”g‡Ã6†ž˜KMÍ K©€B¡HMI=yüÄìVØvë7\¾x© ?_Ìþyôð!õBÆåehÆêëë©¿ÿ8ìæÇR‡e}œ|8{-P‡=}üuRÝ$036Áë“‘‘A%š)oß™srýê5º7NVV–P}uëÆMº¾ª¬¬DæåæÑõê•K—áUMÁÄ‘[èã?¢··×@W®‡##žÁ±ÔO´0  ý©˜€>4¸€aGÜÝÑIe†††š››««ª:;:P¨s {»tSôäñð*§ð?{Žnˆu—h£¸{Ÿ2õWtÁ7o¡d(PŸºÚ:ºÉIV¨eÌ5:;;ÉzŠn £_DñtaMGÁqº°¤ŒŒÐjä8eú:ºƒØìà îßV3šA×®ÃsB½¦vXh¨°=ævðEwêé‹|¹"—Ë龡ñ=íƒûZ³ìÙí}œ½5 m$ã^Ɖ٥áOžÒõ' P'*>‰ èã`AD=΂Æaflr;8¸¿¿*Cý(-6sê[ÈÉê^¥ ;ú8 ŽS‡%}<ý}:ÝT€ÈÂÈ=¹b™¥:K‹¡¡!¸qNö:9ÑUÙ™s:Ÿ?}B5ŠäæäÐõg}}=îkÍ’™‘ }œMÎøøÒu²µÕZ…B!NgÊd²•–Ëéú“Y*6 ƒ…ŽÞÞ‘ÏŠ ‹ÆÆÆð×ý(&Ûº5Vp)DZµYO1âæ&¦¢-ßÇ -µö9» £°¡Çž¤û­K8ËàààÍ FúóWÖ;çw¶¯¯7¯_£3§ÊxŸd¦8>Tãéã'šÕÇg—©´¶Z{ØÍ,¾ÊËÊFFF&q"—Ëç_€/ÔBîÞ…W9Îðt‡”êª*x•Lèã—¯ 5Ó°¡û9C1-ö»¸ lgp`€ÌÒNxy­·¶152Öú}‘®™Ày9ú"* ¯7”Áiç.Ê9{zÄã½’âb¼]PŸ#î‡QÞG¨D¿ˆ†>Î&¥¥¥Ô½óå‹xø%;›ºÅ\;Kä@*sÈÕ•Sùœ…Ë·;8œ<~âîí;d9VŸßÖÚ:99‰Ø ›ŠòrŠYDï---ð*Çéìè +ÂF>‹„WiA74zÚ:d<‡WY€ }ÜnýŠÉñ"* a¦¡¡î\Y$9§³y“=E®Y¹J„'Î(Vø171ÅënN100Àľèãóà¼g/]o;;í÷T6Çí;“¢ú8Pçé¹b™%Ç%ò9åN’«[í7vs¿p>,4ôUâ«ÜÜܦÆFl9áOžÒ|DnÛ—ò§]»©6e9—Ò‚â:ËÒbYIq1\ÊŒë㉄br§{WW„͕K—Ñ™SMbcbèú0##CTl¤Z|\´5ܹ̇mÐÇÙ„z'%b"™.——•Qw: ˆèã@*** õôy'‘Ïc&†Fvë7ìsvñ=írçn|\\Η/ÍÍÍããã7_ {èóу‡p)/ û^d¹Å2¸”%Ð×IIð'k0®“ç+Å›vóÆMˆ6d2jnbJ³3çªÕ"tãØØ™îSt£›ëAQ90îeEï%&$âÖæd }œe·m§ëp‘ŒKG¡ë·M¶vÈF1}¨É‡ôtíÅZB’ÈçÙuî°e«×1Ï›A7bcbȺ¾­µ'9Ýc õõõp)/hnj¢{×wvv«T ¨'¿I†?Yƒq}<ü)Í—ZW3 l’^½¢ûœ ½_œž¼táE7’‡\{{»x¼àïOÑ{¢*ÏJJJ ³LÆÇÔÅ‹êêja;­±±‘z- 7¯ß Å ôq@%‹È,x‰üGmÖ®±:°oÿå‹—ˆr¾|éìè@JhÁì¬-ÖVkq`šƒ@ç)ŒëãgÏøQ¼c3323 lv9:¢3'êëëé®nݸ)ïmwp å72oÃ}ÍAd2™®ôq–Ù¼q]Ÿ{ó¶Ç|OûÐõØÚ5VØü(r *D>¯D>ÛÈŒbó&û“ÇO< {ð)3Š9›äæäP ¥Ÿ¯/\Ê#Îù¥ý°ÐP¸” ÐÇy ãú¸ãö÷oJ$Ä ˜úº:º³Õ£‡ˆÙŸ{v;Qt&YËd21øM¡PPTN}¼½qks§» ³LÊÛ·t}N¦FÍMMBuWgg§ŽÖº‹~<9ÐÇ-"#žA"Ÿ§¬ù.GÇË/½J|ÕÐЀlaºçõàRA÷è9ZFÑú8Oa\§XØÖf=„Í¥‹)wæüüRE{›,ŠçÓׯ_¡F‰ ëס³ŒB¡°YgM×í¾§}„ê.êݪ—[,›˜˜@Šèã€"df¨¯£ 5ü§fl`è´k÷Í Ÿ23±ã.~¾¾#ÕÚÒ—òˆöövŠÑ·Û` —Rú8OaVïëë£x»b"6R©ÔÔȘî~çæææÓØØH·K'™Ù‹!éVI.--ÅÝÍMÈ| ú8û$ÄÇS/Û!ē샆zút}õøÑ#d €>èBæ9–K- €/èäÓæMö—/^ú”™‰w–ê³×‰ÚyYsSø“w,55£X( þ¤ôqžÂ¬>^Q^NñQú,"&1!3fî›Zº?yü˜bÿÀÑÑQÜÝܤ®¶ú8ûÈd²Õ+VÒõüÅó„稻·ïÐõ’™± †#ð ú8`€ÎŽº „DU¸ü°›{lLÌàà I5Ö¬\E+{œàOÞá¼g/Å[²¯¯.Uèã<…Y}ü]ZÅ{5;+†b±~svéÂÁ§âÅóh¹ËjÕjÜÚœE&“Ñ-î }œ}yÐÛÛ+$™›˜ÒõÒàÛÈ=ð ú8`¹\|óEMDl¦»Dûˆûá÷ïÞMNN"45—»|ñ\Ê;/_¡x'–•–Á¥ê}œ§0«ÓmÑÙÙ‰€¡R[[‹™1_Ú I¥Rag£›ëAZîÚïâ‚»›ËX[­…>Î>ããã–ËèMA×® ÉEáOžÒõ¡žþàÀr|ƒ>˜¤°°ÐÖf=fËjžõ ð÷'+#¤“2´µ¶Rt~L4šñؘŠ9òö-\ª>ÐÇy ³úxà•+6ˆ0w옶—±±ÂÎF{»´|uöŒîn.ã²×ú¸Fxôð!õÖgCCCÂpŽL&[i¹œ®/_AÖ) F™œœ ¹sWO[fõ»þ¤¦¤'’jŠ‹Šp^_ä|ÉΦ˜áOŸÂ¥ê}œ§0«{ŸÎS˜ÕÇ)ö<ヵ ,Û0ñå—ùžöj6 PtÔÇpƒs™gÐÇ5ÅÐБ¾Ý¡éñ£G|w ÝÖîÄ´kaC˜ôqÀ2555Þ'OÑmˆ-N352¾"ø>@ ‚<÷)öX‚?y Å“èžÇàOõ>ÎS˜ÕÇ×[ÛÐJ‹ëW¯!Z@TWWc¾Ë;3ÐÕL©ßÐíóSTX„{œË¼NJ‚>®AÈ܆îÐdi±l||œ×>qؼ…®O°Ò3€>4BgGGðÍ[+–Yb­~Å•W‰¯QSÜ ºA˱V«×ÀŸ<…ÄŽV¸ìu†?Õú8OaV§XÄ6,4Ñ‚$Àß3]>šP»—ÔÔÔPôRCCîq.“™‘ }\ƒôöôèëèÒš¢ž?ç¯Cr¾|¡ë ­ßUUV!ÓÀt  "—Ë322Žõ0ÐÕÃ\ZÛ¶Õ¡¤¸xù -—:lÙ ò;Zi°ËÑþTèã<…Y}ÜÔȘVZDG½@´€ðCgNžÚz6 &‹ Š^jkkÃmÎeòró k– çéMkV®’Éd<õ†Ë^gºÞpÝ9f}p©Tš–šê}òõ^Íâ±%‹_¾xIäåV(Î"öìvÂÉSœví¦øæ þTèã<…Y}œâ‹ñÄ„DD —±±˜Ýò×rsr„—“]ÔÝÝÛœËA×,íííÔ‹Ò&&$ðÑååÔGi2 !ÇÀ  N!—ËKŠ‹ï…„8íÚ­»D³ë…šµÕÚÂÂBÑæß™3´<¹ßÅ÷#OÙçìB+ 6oܪôqžÂ¬>Nqɇ´‚„âa(ûv쨇ðr27'‡¢‹p›s™ò²2èãÇÇû4N·<Ž¥ëœs}p–±±12 ½wßíà! 3sÌ´•ßH~ýê5¹\.œ9}ê-7º¹Ä=ÈSHìh¥­ÍzøS} ófõqн´ÔTD ŒÊÊJÌhym:ZKzzz––_²³)ºH¨]L1 A§HSc#Åiô”¥¦¤À È.0èã€/´´´¼MN¾tÃuÿK‹e˜xÿ´I¸/]¼HËo$Opûð;j³ÇmÛáOõ>ÎS˜ÕÇ õôi¥EÜË8D ‰-ööP–…aöíRf675QtNee%nv.“–š }œ#”Sòró¸ÿÃ)N§,6&~ôq N EOOOEEÅÇ¢_Dß½}çì¿C®®dIB2™ú&ùbëµÀ«´ÐÇy ³ú¸™± ­´òÑ‚î]˜f<ÿZ´Kš¬(:';+÷;—‰‰>Îö:9Ñö9»pÿW‡Ü¹K÷W¯Xf999‰t?ú8³!Ãf[kkAAÁ›×o=|xåÒåÃnîvë7èëèòwŠr÷®àw?ä4`¹Ô‚V¸rƒ?Õú8OaV_i¹œVZÜF´€`ð;s²²,èÚuÁ$çøø8EϼMÆÓ< ƒ>Îr¾|¡>:•—•qù'™Ó®–ûôñä˜èã,ˆÎŽŽÜÜÜØ˜˜ëW¯ru]µ|¦èááŽ΋¨(Z¾ÒÑZ‚lç)«'ùžö?Õú8OaVßhkK+-.œG´€0¡~–¦Y[jj&¤íŠ;Ç>‹ˆÀ-Ïe®\º }œSlwp ;:vsçòï ú”îï571C"y€>€š åçåEF<ó?{nÛV.W6_²hñûwï‹”·o)º«¯¯éÍ;úûû)æÀµÀ«p©ú@ç)ÌêãN»vÓJ Ocˆßóøc¯“’“¢ëÖXÑrKà•+¸å¹ÌÑÃG sŠééÔÛ”ÕÕÖqóÇÊd2êûERp¨ôqè2>>^TXôäñcOî.7ÐÕãìsP}èž<+--E>ó޲Ò2Š9ð , .Uèã<…Y}üˆ»;­´Øá° ÑÂ`óÆMP“…g»…Óóâ«ÍcG=pËs™­ö›¡sM¶vtG§“Çsó—ÆÇÅÑý¥FúƒƒƒH!0?ÐÇ`”ŽŽ2¼ŸðòZJ»|–Êf³vD"¤·kkkQQäÐ=Cû.Uèã<…Y}üŒ/­´Xn± Ñ ´´R²PÌP…‘¥§O¢å“­ö›q×sŠm´¡Ó‚̃éMÚ‹µZ8ÙCx½µ Ý_Š+@ ÀU•UÃ8íÜEQ-RÍŽ>"HK$Š^ºrIË;Bïß§˜_²³áRõ>ÎS˜ÕǃoÝ¢X;lbb|‡âK£©>*¬m`*Ý‚‰çý„‘¥w‚oS<ÓªP(pãs“žžº‹OèãT ·ŒµÕZº¡ñ;s†k?ó]ZÝߨ»D»»»ù~ ôqا·§çET”óž½Ú‹µ4%‘“{_¾571¥WÒç>ù‡×1OŠ· 7wTðèã<…Y}<&:šâ½ZSSƒ€^#‘H tõ(Þ'¼¼àUuðô8F1Ɔ£££pKÒ«WÝòõëWd7ÉÎʆ>ÎMâb_R׎;;;9õ¶l¥ûÏžñCæe€>€éïïzþœú#@É\mmmÂs)ÅZy6k×!EyÇz§ñ–,Z,“ÉàRõ>ÎS˜ÕÇ?úDuáýX’M·ÜÜ\xUˆéF$úE´ÜRQ^NÑ'ïß½C¦q“§Ÿ@ç&4®\±’nt._¼ÄH·ŸØT ¼ŠìOÆ  2µµµþþFúlJäûœ]„çÉcG=(Ê£###HN166FñL™|Â¥T€>ÎS˜ÕÇëëë)>Ïnݸ‰€^C·ëÞðSÁf5Å ØÛm€OÈÌXë÷E´|tý:ÒŒ›Ð=?}œ.‘ÏèFÇ@W¯¿¿Ÿ#¿Îe¯3Ý_wܧ©€²@€;H$’G®´\ΚDž˜(0ަ蟬¬,¤% »á`¿‹ \Jèã<…Y}|llŒ¢ÈrÀeøKIq1ÝéÝãGàUõ ò”n\H à–µk¬h9d·ãN¤7¡¾…>N©TºÌ|)ÝÝ ºÁ…ŸF÷„ 12Õ¬®®FÎ%>×ÉdÑ/¢WÓ>85§‘Éy É{©)©ý|ë’G„ܹK1úׯ¥T€>ÎS~aú Ö¬\E+3L 0À_|¼½é“åÎN@^344¤¯£K14Þ'O À-žÔŽj÷NNN"Ó¸FGGõ5'ôqº< {@7@d%‘H4þ»<Ž¥û»pE¶å>7!ÓÅ'›3-‘? ’ßš››):Çiç.¤"pvÚC1ú¯_Á¥T€>ÎS×ǹºR¼cëëë3ÀG†‡‡évæÄYrŠÐ}u¡¯£;80ÀwŸ•EŸäåæ!͸F|\ôqŽ322B]&½w_³?ª©±‘âšaÊ ‘-@y Àeúûû½OžbT714’Ó õôi9GGk Y·"ù2QÔ]¢MñÖÀiÎSØÐÇïߦxëš›š¡Ä àáOŸÒÌáCTUVÑÔÝÛwx퉉 º{ê+++‘f\€ú~èãLÓÖÖF±À”%½zÅæO#ó7º?áel,rhVhƒ>.Tß¿{GÖ°¾§}NxyùŸ=|ó6{$§òêó–Èg‘‚ñÏÍ t_´µ¶"ë8KWWÝ™!ŠÓú8OaCÏÊÊ¢û$ËÈÈ@ä_Ø`mƒÆ|nÅmtþ9zøNÿæº]AgŽÓ§NÑ –Ýú l^?õWÅ+-—kªŠ:à;ÐÇÁ<¸rû‘ð¤õû¢m[Òß§ÃQáÀ¾ý”K¬ìÚ-çQîwu-ð*R޳ߢüºíÍè}œ§°¡Ó­/qØÍ‘ü˜dççS~t=À£‹AâãéÆë]Z2½ÛÏÈÈÒL³´´´0QÄú8Ó444Ð--Jìý»wì\¼L&[µ|Ý‹òYTú8˜‰DrÜÓKÉÐ;lÙÚÜÜ §±Loo¯¹‰)Ý®NãããÂpŽB¡ {NËÌØdll YÇA&&&ÈӇ¾¾Ž¥ôqžò ;_sÀe ûròø ºs¸¾¾>x•9¤R©©‘1ÅísváµCèžÝƒ¤¥q.]¼È8}œiŽõ ¯­ö›Ù¹òø¸8ºWnnj†E;Pèã`6­--ÖVk}C£ô÷ïá:–‰‰Ž¦û@)ÈÏŒs¼OR>jöøÑ#¤yE½- ¼Jèã<…%}<ü åsµ·ƒƒ<Àqô´u(¦½§Ç1x•i._¼D1dZ¿/âûö"ºGYW-_d``À@Wú8O©ª¬¢¾÷?ëóg®œn1b!wï"€Ê@³gìV«V«Ú‹µRSRà@6‘Ëå6ë¬)>PÂBC㜌é>m-—ZH¥Rd§ +©Õ+VÒ tÐõëp,] ó–ôñ††Ê[‡LL±upœ§ŸÐMû/ÙÙð*Ó466ÒU ®òÚ!Ô7~&ÄÇ#Í4ÝvÙÐÇÙÇuÿÊ¥W™oÇô.-î5é !€Ê@3p?ä¦rèêÕÔÔÀ‡lõ‚â3刻p ·Êd²¥´[a‡?ÅÑOnC}_[[ ÇÒú8Où…µo²µYÒ“@TÐÝݰn\ÊN»vÓ}™Çëʆ‰„îŽãµk¬°…\# šAç5E…EÔ£VPPÀè5oÛê@÷‚Ñ1 ¨ ôq0ÌŒL5Ó€Œrp#ËSSнÍì6Ø É9ÎS/h6<<Œ¬ãcccË-–Ñ ñ&[;8–:ÐÇy {úxÈ»tïäË,qÞp–¼Ü<º ÿ0ì¼Êä!D7v‰ ‰¼vÝ2úÄ"ŸE"ÍØ'ðÊFÅqèãì@÷±ûö3wµ9_¾Ð½Z=mžž¤Pèã`:»ÕÏ„ô÷éð$›<àJë.6ÐÕ’gJŠ‹©Ïîø~VHP—Ôˆ=zðŽ¥ôqžÂž>N½Ä CÀeŽ{zÑíI‹Îœ¬199iafN1|;¶ñÚ!Ô.Ë¥£££È46éèè Ûú¸¦ÈÊÊ¢¸ŠŠ †®Öe¯3ÝK=çw9Ôú8øÎׯ_©dÂÁ®p&›<~ôˆâ“¥»»[Hα[¿î“Ww‰vsS²NãtvvêéÓ ®ŽÖl;`èã<å6¿l£­-ÝûÙÔÈ5(éïï'3 ª9=àU6 º~î`U]UÅk‡X[­E…^ãqä(Óâ8ôqÖpز•nàHz0qååt¯S{±VKK ¨ ôqðˆðpZ"Ž5³ Ý­U•UBr΋¨(ê¼½NNÈ:sØÍzd½Žy±L}œ§°ªSoWHìâù ˆ"à>DgN^ÓÖÚJñ©&€=ÔGo­%uµuÈ4vøüé â8ôqÖHÿžnàÈp×ÐÐ@ý:©¿•9áå…èõ>¾ãéáA+ áO6'ê.‹Ýèè(ýfâãâx„z·s \Œ}œ§°ª P?ß­½X«²²ä/‰dddD`?ŠîfÛµèÌ© ìÛO1ˆ†zú$Õùë¡¡!º]:‰9íÜ…4c©TJ}û?ôqc·ò<ï“§è^aSc#Ý·ŒZ¿/ª©©Aèú@ß±µYO+âãáOÖ£ø|ùüé“ÀüséâEês<3c“ÎÎNäžF Þ–“Øæ›à[†€>ÎS~aùûN?NýÆÞîà P(KÑÑѺÛqçwÅMO[Çfí:ÿ³çÞ¿{'—Ëyýë¨k~†œaê;4É‚œ×¹pžúèŽLcšË/±#ŽCgâjêG:ÚZ[)^á_ºWèæzqT€>¾C±˜oø“§ð'kµ?ÅçË»´4ù§µ¥…<Ö©Oóœ÷ìEîi„#˜·'&$À· }œ§°­çåæ1qo?zˆ®»ü ³£ã„—×üìµk¬â^òø—§Ç1ºšEoo/2‡}ärùJËåCi·Á–×iii¡»”˜®þ0J~^õ¨AçȵnÝðœ;Gëòºººè6á V\T„¸*@SLLLP£Bï݇Ky»ô÷éÂsÑéS§˜˜é=~ôéÇ2/cc™åÚ5V|ß•Èe ó”_ØÿÊÍ7Q¿½õ´uêëëN.£P(ÂBC•/ÑpÄÝ}xx˜w?³¯¯îëúcGÑ™Sc„ܹKw¤*((àµC(–éün[¶NNN"Ù˜`hhhõŠ•¬‰ãÐÇY&6&†úTª»»›Êµ^¾B¹Ӯ݈8 ôq0YhP¦‚oÞ‚KY£³£ƒbì²²²„ç"êUξïÜBÅj6©®®¦^ârÊ^ÆÆÂ½Ì}œ§h@óú wøæMöˆ(7pÞ³w¡1Ý`m380À¯_ú0ìÝÄÎÎBgNÑÕÕE÷mÇÉãÇù>EÓú}Ú,󷃇ØÇ¡³Ìää$Ý3.į\QÿÂ)–,jqX A ƒÿV‡)ÿ³çàRÖÈÍÉ¡»¢BažO:îéÅÄdo¹Å²Þž$! ­¥}XpÊV­X)“Éàaæ€>ÎS4 Ëår«U«™¸ÏÏžñCD9HUeÕš•«T‹©ã¶íããã<ú±tŸaV«× 4ËwwŠÕ]¢Ýß߇̶Ԕ$]=|Ȳ8}œ}"ÂÃéFÐPO_ý×Ò!w)Ÿ¼ÙboXŠ@SÐÕÇìÛ—²FXh(ÅØ}ýúU^jjld¢ 9±]ŽŽ8ýÉ4 …âàW†fìØ<Î4ÐÇyÊ/ùVŠÓYMTN‘ññ£‘¾:1½Ì—›•M7ŸÉä)¤Y²>¦Ó‡axíêêj&Nk’Q‚|2òŸ?}Ò^¬}\ðH¥R 3sºA ¾uKÍK275Ãû3Àe ƒ)èêã+–YÂ¥¬á´sÅØ‘'—PuÞ?€¡)ßÉã'‡Œrñü†b·ÑÖV¡PÀÃŒ}œ§hFŸ˜˜XÅLQT}Ý’’Ä•#D„‡«¯Ñèêuttðâ÷z9J¹3'¯q«Õkp&`:'¼¼:臄§BCCƒ‰¡ûâ8ôq@w1S#c‰D¢ÎsŸîõج³Æ*Ðú8˜‚®>NŒ/ ¾ÓÙÑAQ{Zjj&`_õ÷÷37'DÍ}æxòø1sÓõ¬ÏŸáa¦>ÎS~ÑÔÇÇÅ1tÓ©j[[B«Yärù…€óÙÎÆ½==t°y9ŠDâÔkÊÊÌäµC¾~ýÊÐiÍm[¼…‡¥¨·—îèãG"‘P_ú> Síbd2Ùªå+è^LÜË8DÐú8˜‚º> ¯²ÀÍ èÿ¬<ÔߣO·Èg‘HHê¼NJbâ´.*A± ôqž¢1}\¡PØ­ßÀÐmok³~ppÑÕàtó€Ë>º‡€¸ÿ«Cïß§üjWˆ½ÔùH¿îmŠ‘u;xˆï>¹|ñs“6´‹Q‰D²y“½¦Äqèãš"øæ-êû T{S•OùXÉòu ƒï ºC–ÓÎ]ð*Ó ™SŒÚyÿa{lbbÂf5C?­ß‘G?Ò’"ïÒÒÚ‡4Õ «±±Nfèã<å ~wúûtæVéÛFGG`ö)/+[Í@ñî¿ð Ûu–|r‰;Ðíÿ®½X‹ï'pÉýhflÂÐèM¼”Sññqºå8¡ó…C=}º¡|¡Â•l°¶¡{ááˆ/ ôqðßÉ m}\ë÷EÕUUp,£^¾B»{Y‚à–››K’“¡¹Y×¼M†~G‡O™™tweͰ»·ïÀÉì}œ§ü¢Ù¯ßïâÂÜý¿g·Óøø8bÌ&Ñ/¢õ´u˜ˆf}]—øçOŸèþÞÐû÷‘NÜ!?/ûfð,"‚¹ÑÛ÷´²nA‡ÝS;ÐÇE®¬Z±r¡·ß¿{G÷–šš¡à`èã` êú81÷Cnp,sTUVQßZÛÖÚ*×ùxŸfnú§½XëUâ+ä§š|HOgHH™² Ö6“““ð3;@ç)ÖÇ›››œ÷ìC˜Y`ddÄûä)æB™Ÿ—ÇåŸÄý0:s ºû"--–ñ½h€\.g´”™Ç£)Ÿ’Œ3ú²ú8÷éêê¢>›Š‹}¹ kضÕîÜ Ad@S0¡ûžß2T*µµYOýe°x²ÝÂÌœ¹à’E‹ccb¥*ó69™¹²*S§[ òóágÖ€>ÎS~ÑøP¯›9Ãvîp”H$ˆ4£Э.2Ûª*¹{\±»»›îóìèá#H*®N7¥SSRøî“’’æºÇ;yüJÿ”‘‘g§=\Ç¡k–s~géFÓÚj­òï¨rsrè~»±áÐР˜ú8ø®2ñ(´03'K¸—:>ÞÞÔƒu!à¼x˜þþ=£“@­ß©Üß[äDG½Ð^¬Åht®\º ?³ ôqž¢y}\*•Z­^Ãèp°Õ~3vã2Ääääõ«×ÕȦ·\®?~?äåΜŸ?#µ¸Æðð°®Å(ïur€[Îû0zï<àŠ óÐ××GpÇ¡k–Ö–ê;Þ¼~£ä·ïs¦|‚L-SÀÐÇÁ éãSkOœ`¦ËãG˜ˆÔ—ìlQ¹Ñï̦§‚çÎÉård¬ò]¿ÎtP6ÚÚNLLÀÕl}œ§üÂ…‹(ÈÏgZ`]½b%Ç Xó‘²Ò²M¶v,h.¶6ë9ë…BA·):srßÓ>tßú øèèèš•«½ý·mu@úͦµ¥ÅÚj-wÄqèãçÔ‰“tJñÊ|oEy9ÝïÕÓÖéÁ¶ÀÐÇÁÌéãÄÜ‚JH 2»`¢½¤åR ±S$óöuk¬˜ž ruAÞþ©TêuÌ“ép9Umm-¼Í2ÐÇyÊ/¹ŽÀ+W˜L °-—‰$Àߟé·ÿ¿™á-î63ÌÌȤܙó:sr”ÒÒRº±¾|ñ’Üò%;›‰EËt[»Æ /8gPXXÈhIèã|¤¾¾žúsY™BºÇŽzPß}†hæ€>¦`T'FÆF”‰SŸ´ÔT†ê2]».ÎåŒîm¦'„¬mšø¿ ˆQÚÚÚíäôÝ"ÂÃámö>ÎS¸¢OLLPo¸1goå°ÐPD]MÞ¼~ci±Œ5Á…L‰Z[Z8ë ÷Cnt,6ÍqºóS#ca¹táÓ〱aÆÇÈÀ)â^Ʊ°¶>ÎGŽ>B7¦ÛæÿÆæ¦&º¢<™ªqù¡ôq0Óú81²L@™8uHLHdH×ú}y~a dto"zÕþˆ/ÙÙæ¦f,DÁë˜'¼­ ó”_¸s)ÕUUú:º, GÜ£c§j”—•9íÚͲàâãíÍY‡tvvÒm¦A’iÆeb¢£é¦wÜË8¸e||Üný¦‡2ϸ¬|Ã@A211àïÏAeú8G¨¨¨`¹<«Ÿ¯/õƼˆ#à‹6}œ×° ÛboOÖ ð¶ ÎM¸ì£~Óý軺ººèe Ó TÉL}LÁŽ>þGk‹e¹¹¹p¸òÈd2¦;IˆÙÃR©”.bS¯ˆD»Uííí;w8²¦¢|ýú>×ÐÇyÊ/\» oov† ’²×¯^GÌOOOÏ•K—õ´u4¢¶¼Œå¬g Å*ª9׬\…|ã>ÔwïV”— Ã3¬½à\f¾4ýý{±%^bB™érY‡>ÎÈšŸzd‹‹Šæü.êÍcÜBÓ@S°¦O-<ƒ®_G9reèîîvܾƒÑpìÙí?·µµ‘Œü7ÐÕ{òø±ÈÏ€F¿ˆfm&¯£µ»î4 ôqžÂ9}œÍ—™Ä¬­Öææä 椽½ýBÀyvö„ÎiÇ=½¸ìŸŒéþÞû!÷uܧ¦¦†nÜý|}ãÖ^pó>yjxxX )×ßßÄý0Ç•qèãœb·ãNº‘=äê:‡´48h¤o@÷‹JŠ‹>À4ÐÇÁ1õñ)#‹Ü½nÿ]^ed0Ý~\ë÷Eåeep5¡¬´Ì@Wµüßá°­¾¾^„~nmiqvÚÃæPƒôÖ,ÐÇyÊ/¼¦öövÖ^fN=#}¼O÷¢)â4È£ËÇÛ›¡v(Ê×àxC7׃èÌ)N·m§»¥B0:ïøø¸Ãæ-¬Ë-–½yýFØÉ·”•>ÐÇ…ÄçOŸ¨Ï”ª«ªf|KÈÝ»ØÐøôq0ûúøÔpêçë;00ÿÏ`llìBÀyŠyžðò‚·¿óþÝ;ºM¶ºà½~õÚÈȈHÜ+•Jo³¼ÝðfÐ $¶Æ>ÎS~áæe•––²ù2“˜‘¾Aè½û"ï0.“ÉÒRS÷ìe¿Îø [omÓ××Çe_uvtÐîÌéŽñˆ/$&$ÒMøg‚qNgg§åR 6‡ 2d556 /Íjjjv9:òE‡>Î5¨—ª÷ô86cÉgNûÍMVVXú8˜B#úø”†Ü¹;::Š(L‘•½v;E™±!iáOŸ²œÿË-–%&$¾ÜJò›dºµX•kr~)Í ó”_8{e©))ì«´+-—“IóÄÄ„Øò ³£ƒÌÒÈÏç‚Âbmµ¶««‹ã»|›î¯þ”™‰ñˆ/Œ››PŒþk!ù§¤¸˜åœ:ZK.œçøK5åéîîöóõe§µ=ôq¡ò.-zñÜé/¢"ÂÃé~þVû͈`èã` êãSfafþèáC‰D"æ(477vsgÍçQÏŸ#ógs;8˜ýü·Û`›þ>]þ$ëú-ööì»Ôýšpèã<å._Ü“Ç52Y™RÉŰ—|xx86&Æi×noŸ¾Bæ¾È%—Ëé¾KX½b¥È;–ðŽÀË”»Òååæ É?©)©lžÖü¾ëNðí¡¡!^¯Õoݸi¨§Ï;eú8±[¿n|}¼OO}2Y}Qß•–šŠv€>¾?s¹ðè414 º~½»»[lþoiiñ=íÃf=Og§=Hûqùâ%äÿv!©ä;whæôç>gîòä,ÐÇyÊ/¿¾»·ïhj²bnbz3èFgg§ð¢>44ô:)éèá#zÚ:œÒV\ö:k¶™T*íïïokm­'ÔÕýÈb¢£éþð³güæù:ŒüòCD^/h ¹\N¾³££±±‘¢‡ÓRSéæÀa7÷ù¿‘\WW¹Aøò*…ýÓšßUr²ÎäÝ^òžžž«ì+ã¦FÆ?íу‡ªÝPÍÍÍÄâ©GÉI¯^Q?¨QPP@âE}ûÂza¡6ä>%w+¹géÎ[Ø´[7nR\,ð×dª;5]‡>®q#¬Ç‘£YŸ?‹ÁóÅEEäDz|NŽÜ­Ü? ¬Yü|}5X[5.ö%Ç"™LF&]›lí4åÀÝŽ;±ðª>þøÑ#Ï4ø6ù…û¹xåŠf'+n½÷NgU¾~ýþäé^''Í6ÞüñÆ4ïÉÉIögƯ“’üϞ۹Ñ®NÄ314"?íœßÙW‰¯DÒ H¡PÞ½}ç°›»µÕZ>©øé d·~ƒ×1ÏGÖÔÔp9ÔË)ozÚ:dH)/+ã~Æ–Ÿ<~Bw‰6û^Z·ÆêCz:§ÒÛ@Wo«ýfï“§žGF¶´´`z­æÛA«Õkx1¬ÅÇÅ!^œ…¬až<~L:mm52RÁX°eæKÉáBÀùÔ”ñ”ûà”>þݬV­¹s·¹©IŒxfo·Q#mQÓß¿ÇxþÓ5”÷i &¿™±Iàå+üê*ÔÖÚz3èB5è7§»°ÅdöhSPP|óÖå‹—üýÙ4î”gà¾YZ,sÞ³÷ÒÅ‹i©©Ïä_x‘ßçÎi¥Réðì7BѬmÞdÏåSWš:­ùݶØÛG„‡÷÷÷sÍ3ä’È…mÞ¸Iƒ[uº»»««ª¸œÞ;w8 µ6%;D¿ˆæþ Fm”Ëä&©)©[¶bå&6Ó×Ñ=}êÔׯ_¡kÖÈÝ÷ôñ“Vþ¿*HˆwÝ@ƒ;´‚oÞ®$çý4›ùZ¿/"Ó¿QQƒÞÚE®L±ö:9±_Or†pÙ‡ãß)*,:çwvµÈÔ!™¡ž¾ß™3m­­Ðǹ.²LßaáöÜûwï8»½¢··—\ÞÕÀÀm[¸¿y–,ŒËJYÝã©P(îÞ¾ÃÍMô,I‰ k×…'FˆùY¸ÛqggG7CséÂ.¼:äêûRãuWÈË £Ù!ÈÖf}oO¹ŽëãÿúïÛ/˜æ«,399¹b™%Çãû,"‘âÝÝÝÎ{öb&f#©{!!ÂîŽÃq}ü»Ù¬³¼|%++‹G*Yh‘rܶ]ãâÁ®Õ„fïO…¸ì‹Œx¦A±lmmmäzÈUqDIð8r; ¦¨¬¬$Ëa<»…aºK´ÃBC52ù…GI3è×&Ž»ƒoÞÊÈÈÔ g†‡‡óóò"ÂýOžâò>ñÙævðËû:'&&Øl’ÎYsvÚ3::*˜'b|\œh_xLuWQ^ÎQ‰üâEŽx‰,Ò·ï¸{ûNAAkJòEäëBîÜ% ¯‰m²µû>ðòBŸjbÜÜ܌ٿ „?yÊåÈ.55þ'®QSSÃý×*0vìˆûaö‹BŸgí¹Õ~óå‹—RSR¸£N¯öñÇÛÁÁ\öépÄiÄcBZò°™µrª@„­ÍúK.¤¥¦²_/”|#ù^òíÔÛž«]œö´\.G®~ûOÛmHÂ3¼þù…_©?ä7ƒGžë­mNxy……†fdd´µµ1÷º£¿¿¿¨°(>.îfÐ#îîV«V󱼑±a\ìKöSÈÓÃcÍ”ísvÆ3•,¸ 9rÁÌML¹Z³/øæ-®¹Ë@WÏi×î«o^¿©¯¯§(;«©±ñmr2ùpòìwݜǷmŸ~^•/úø”D>µç,ˆ±±1sS3Ά5ôÞ}ĈS´µ¶j¶Ž*ŒkFfÎÐǹiSM†üý£ž?Ïúü¹¹¹™Íÿd¬ÈÎÊ~uùâ%ç={-ÌÌ9袵k¬z{{1°«FB|<5G­ßÙ¬³>yüÄ“Çsss™ˆoOOOnNù|ò-ë­m¸©´ÜFŠNñèÁC<©…j'‡>ÎË‘zÎCdì>xÀõì?2~=ŒLKM%ƒxEEEKKËÀÀÀÈÈÈÄÄÄôyYÄ õõõ‘ÿ ¼¬ŒLtÞ&'G¿ˆ¾{ûŽŸ¯ï—}vë7™}—££Fªé‘9F™év/$„÷+ù¶6cC„rúÖ`Înõ ò”Ë/óÈ“eƒµû!·K.$·zÅÊÝŽ;==<Èd&ôÞý¸Ø—ïß½ËÎÊ.--mhhèìè$‹P2˜!¦“ÿw||œÌmzzzÈR´®¶®¨°(##ãuRI²½pž|¬ã¶í+–Yr¿€çT Oî²ç_²³¹/>+tزõ¸§™‡?yüøÍë7$á§$rMOõ)É…üsòoÉC¦ú)oßNÍ὎y’Ïáþº’,Xâ㑜S|HOG'LaÛËØXèãB©4•Ï]m¨§ÿôñÔ252Æ3Ýôut;;;yýP$SÄq†E„‡s6^Éo’9«+¹i…¿‡.ž¿0{ìå—>N í:U@"‘psÖtý:¢Ãµ=(x†Âf™?“Y4ôqAšöb-AžÂ\i¹¼…ÿ­M¹@mmí*AtxFž“éÜ—ìl¤å###8ñ&üóñ¦flö}ü#5Œ5ss=Ø¡¹‚=BfÛ  þ>;;:PYeÎý2\n¨UXXÈåj‚4­ß=zøpÎpðNßåèˆõ€ p­ƒËÔ ZÌá›lí0`Âæ´§Ÿ@‡ñÅÖ¬\…ž%éïïß³Û y¥q³Û`‹ÄžgÏGÂø»ùï^Ô{0RóÃ,-–¥¦¤h6a6oA f›ÕªÕüÈX‰Îi\\kK T6wšÌ³çšwú8±®®., T˜2èêq*Žþþˆ §hjlÄ€ û‘ípØ}Æ ±»»C:]d2Ùå‹—]´cG=Ðiv«±_Væ¸}ôq¥Ëåׯ^CÉ!ŽWȺxþ›g"ædll dü-±B& ßœÆý–wR©µqX0[›õMó¶lå£>þ69KàÔÊ–Ì P–kÄžĘ ›§ Çøø¸Àr~hh‘˜9íÜ%ÈZ@!éÕ+NõœœòèÁC¤ß êëë‘⹦7n„>þRSR…TŽ\Hæì´§®¶Ž IRSSƒpüÈrssyzïoÞ¸ á›Ó|¼½yÁȈgºK´/†ÌÓãç;Mø¨  ±°FèììäÎívêÄID„kÜ ° ›Çç}ÛÊG¤R)Â*$ó=í3£é( NsSÓ{{$›G½KŠ‹‘x³y›œŒô544@_ííí»w"o8U÷--5•;RŸ üÈÞ¥¥ñôÆ'i†ðÍiî‡ÜøÄŠòòµk¬2º¦»D[ÉZ±|Ôǯ\Áª@5üΜáBµ~_T_W‡pp€sç0xÂæ±’’᥽¾Ž."+Œó ?골399xù Nð³`Ç=½4~ Ÿ³<‹ˆ@†ˆhÂÖ[¢_s‡(Š{!!:ZK=š5sS³'³vBIŠ‹ŠšÙÇxzׯƒ®úó8r”Gq={ÆQ£evl««ª”t>õñ k×±*P––íÅZàa7wÄ‚ƒ\<ã'l«(/^Úc&)[f¾47'c8Ë|þôi¥år¤Cfbh”˜€4ƒ>›²ò²2èãªPVZ¶ÑÖ ¤3ÔÓ¿ÌÍ—œm­­ð<»¾9íBÀyÞE3ããÇË,;5·å^¾xiA%bù¨‡?}ŠUÊœ<~»PÁœ„Þ»Q6ñ·]Í<¸rCdym»w¢§¦ «þ³gü°‘œº¹<„^ô?åÍë×HÌ@ ÿ™Lv/$DO[iÄšèê^¾ÒÛÓÃåÄ052F¤æ¬Ã •Jyz³_8Îiq±/y:Ïö?{ólÕlÕŠ•9_¾,Ôç|ÔÇóró°*P™ºÚ:ÍÞbÎN{nòñà ¤°yއ 2í†= âŸ•–Ë×[Û OØ4­%a¡¡ …£·f!“O«U«‘´FÚ×IIH*e@{9ñØRg ¿õ†illtÚ¹ ÉÄžñëW¯õõõq?%Ž{z!^³Íe¯3ó³%‹óz“Waa¡½ÝFÄqAKD2©àmÞéã¦FÆ“““X¨Ãww F0;+!à&###¨Å û‘<~ Ë“©×›&¦Á7oñhÏÍÍEÔf[jJ*op²Yn± Aœy(Ïõ ߇n¹\þ,"ÂÌØÑü©írt¬«U½Õ!ïôñ+—.c= &åeeš ŸÃæ-ð?—ñ>y ƒ*lNSá|_ØboOÑ?ƒΣ!£§–†= KŒØ\£µ¥å°›;RTÛ¹Ãï{T èúu$¬¨°ú85&&&ÈCÔÄЉEËlÖ®{ÅÇ¢¸"|ÓÍqû¾ßàÑQ/Ç馽X«ª²J£÷ÐÐPàå+ºK´Ö9ÍÒbY|\œšNæ—>nfl‹ãJÜgŸ³‹F"ø.- Îç2ÍÍÍra³mÏn'§}jJŠšþÙj¿yæ­ÔÔtòø‰%‹#yèÚa7÷¶ÖVŒÕ\&ëóçÍ7!W•´5+W%½z…´Q ²(€Ê'†aŸÍ¤úE$7Oÿõ«× tõa*›Öï‹ÈŠ:ý}:Ó ­­ÍØÀ¡œ2=mu¶r‡½NNˆæw ¾yK`£7Yùx{k/ÖBp¿™ †Þ»Oå%%¿ôñ¸—qß òóòØßkxžû< à ›nFúÍÍÍxe8O‰³â¢¹·¶544xó„JNŶ;8`ˆæ o^¿±Y»y;ß6—¥‘ÏP3PM^ÆÆ"—„½æe¹lì/¢ºúúú®B%W¡” ñÛׯ_¹99(Ž65›ÿî0îëÌÀ¦ìèá#BíST__ïéq ‹L=mÀËW(¶â‘>tí:–ٹÑå&&$Àí¼àäñxžÂ¾O322Ÿó===kV®RÍE‘Ï"çÿð¦ÆFÿ³ç°úPÙ6oÜÄëj¢E&“ÅÆÄX­^ƒž)­˜š…Þ¿¯Zß 0›K. ©„ZM« ?ŸåtúE„·Pooïíà०fȹùmÉ¢Åûœ]^'% )ÈDßHß@ä›ÀZc“›ÚaËV‘ß°¾§}_±±±ÑÇÛ[œe=ÉÌÿ:::躔/úxà•+XÐ%3#“Í®^±åbù‚B¡8wÓ`˜‰¡(D’öÍÍÍ •ÈÉB)êùs%?¿¯¯,?-ÌÌ‘WÊÛ‡m?|À˜Ìkärù›×o¨Tù€‘¹PDx8«ÔrœAAZ¿/B‚ ÉŒ ?úÄ~.ý"Ú»ˆ LÑ/¢7XÛ ùf›Ýú Ãtuu vÜÔäì´GœÁݹÃQeUfoR¾yKœÛs,—Z$&$Šgôîèè¸(žzsËÌ—Þ d™Ü×ÇÉZ"ýý{Lý™`ó&öÖ«?Ýb ¸ÆÛää•–Ë1%­pÙ'¶BÏýýý‡\•íTd³v]aa¡ “Urgíur‚”3é.Ñ>yüDii)Æa!‘•íºÿ€hO‚:lÞò:)I.—#"ããG²dÀø‰ôquÉÍÍ%Ï`œz›’ÅïÞ¾S[[+žÐ{óÉ^r]½£‡dee ;¦]]]Á·n­[c%’–[¶>Œç=‰DñLØï8·ØÛÇÆÄLLL0çFÎêãÚ‹µv9:ÆÅ¾dô狜ԔTÖÞñ`·™ÚJ²Ãaj[‰jÏ8Y‰¹Ðóûwï¶Úožÿ­-™~¨y ¦¹¹ùÖ›*uªÙ¬³½¿··ïPikm½tƒÌ D’Ò†zúgÏøUTT ô,@– /cc·mǤ…§fjd|úÔ©µô€>ζÔò"*ЬÄö>Ÿ Ûî‡ÜkhhíHZXXH¢Þ?à°›»óž½Ä!mmym[¶’B~ŽÿÙsÏ##ÉX‘œŸòõë×Ô””»·ïxó<à²oçÇÍ7ñ:¦äúw;î<äêzòøñÐ{÷?~øÐÓÓƒ¡ûÛ: zŸ<%¤Æ&†FÎWUV±à½ÆÆF.¤÷VûÍN»v“ô>ããþäivVöÐÐr›ö»¸°ßȈgp5¯éëëËÈÈ =uâ$¹OÉݺÅÞžï3%Ø&[;2ã%ÓÅ#îî—/^Љ)--E¤)š#ÂÃÉì‚Ì!ÉŠL#>rçn^nÝ/*(( susq—ý\f¾4À߯ÅÃäädò›d·ƒ‡„Z2Që÷EdÐx%‘HnLZÈJùaØßÓ>S“2Œã¡ÏÙÈÕÀÀø¸¸Šòr.°€>>“ÎŽ²8wܶ]ØB¹™±‰×1ÏW‰¯(öyM122÷2nŸ³‹öb-þ(vÝ 1!;mñ —Ë ®_½&ª†ó«–¯¸|ñR~^žP{˃Ÿ280ý"Úi×nÁløµ·Ûø0ìA{{;‚ >þC:;;Éx}Äý°`ªÜ’âvðГÇÙÙ–ìÓ××G†n×ýô´uørôÒÓÃãuRö˜"§©±12âÙa7wAöY1ÐÕ;ಬFëëêk0}öž˜àuÌ“i¯£µÄyÏÞˆðp±õl@x@ÿ92™,?/ïNðm2ðêéó«vŠÝú ¾§}¢_DWWWãå<@<Œ¥¿!༵ÕZ×vزõúÕkŸ23±[3Ëå%%%O?öôðàu³\sS³Ãnî>,).žœœDdÁüÂK^nÞÝÛwö:9q¹?Ü’E‹íí6^ºp!-5\ ÐÇyj£­-×jfèê9lÙzöŒßóÈÈ‚‚ Ö@hoo•øŠŒvë7hê§±ánÇ—¯¤¿OFP $i©©·ƒƒÝZ½b%g ’ [µ|Å}ûƒoÞ"Œµ@e&&&òóòÂBC==<Ö®±ÒxΛ›špÙw3èÆ‡ôt´É@@WwÔ®¨¨ˆ‹ º~ÝãÈQ{»¬m0771%_çæz0ðò•QQÙYÙ(t?ell¬   2âÙÙ3~Û:È©»DÛfõ!W×kWß¼~ÓÔØÏ€ ÃÃÃ%Åů_Ýö:æ¹Õ~ó2ó¥, ˆ:ZK¬V­Þëää{Ú'äÎÝ×IId]<::Šè&Ìùò%"<<ÀßßyÏÞUËW0šð¦FÆd™à}òTè½ûïÒÒð¦1}œ>½==drðñÇèÑd®àö™µ¸î?°s‡ãF[ÛU+V.553142ÐÕû¾ý|ɢźK´ô ÌML-—Z¬]ceo·‘ü÷\ö;êA>L}"#ž½yýæKvvCCf@‹¡¡¡ò²²Ô””ð'O¯\9yü{ÉR“ Ådµi¨§OÆç©]çd"®¯£Kp2Œ“y¹µÕZ2u>äêêãí}500,44îe\nnngG¼ 6™˜˜hii!ó²f$+Ç»·ï\8OÖ¡dVCÖ•du95±™Z‡~ŸÛL_’Nþ­¥Å2ò_’u+™ä8ïÙëéáqöŒßõ«×Bï߉ÉÈÈ KÝžž”îšell¬¡¡!çË—W‰¯È$üâù '¼¼\÷pܶ}ƒµÍŠe–æ&¦$ŸÉÔýû4~ºäB¦ñ[¶îsv!~Îï,¹_¦Ò»ª²j``î@„@¯s?ú81}€>@Œ@ F #ÐLjèãÄôqbú81}€>@Œ@ F #ÐLjèãÄôqbú81}€>@Œ@ F #ÐLjèãÄôqbú81}€>@Œ@ F #ÐLjèãÄôqbú81}€>@Œ@ F #ÿ/÷ùîD¿'IEND®B`‚cadvisor-0.27.1/machine/000077500000000000000000000000001315410276000150165ustar00rootroot00000000000000cadvisor-0.27.1/machine/info.go000066400000000000000000000137401315410276000163050ustar00rootroot00000000000000// 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 machine import ( "bytes" "flag" "fmt" "io/ioutil" "path/filepath" "strconv" "strings" "syscall" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/utils/cloudinfo" "github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/utils/sysinfo" "github.com/golang/glog" ) const hugepagesDirectory = "/sys/kernel/mm/hugepages/" var machineIdFilePath = flag.String("machine_id_file", "/etc/machine-id,/var/lib/dbus/machine-id", "Comma-separated list of files to check for machine-id. Use the first one that exists.") var bootIdFilePath = flag.String("boot_id_file", "/proc/sys/kernel/random/boot_id", "Comma-separated list of files to check for boot-id. Use the first one that exists.") func getInfoFromFiles(filePaths string) string { if len(filePaths) == 0 { return "" } for _, file := range strings.Split(filePaths, ",") { id, err := ioutil.ReadFile(file) if err == nil { return strings.TrimSpace(string(id)) } } glog.Infof("Couldn't collect info from any of the files in %q", filePaths) return "" } // GetHugePagesInfo returns information about pre-allocated huge pages func GetHugePagesInfo() ([]info.HugePagesInfo, error) { var hugePagesInfo []info.HugePagesInfo files, err := ioutil.ReadDir(hugepagesDirectory) if err != nil { // treat as non-fatal since kernels and machine can be // configured to disable hugepage support return hugePagesInfo, nil } for _, st := range files { nameArray := strings.Split(st.Name(), "-") pageSizeArray := strings.Split(nameArray[1], "kB") pageSize, err := strconv.ParseUint(string(pageSizeArray[0]), 10, 64) if err != nil { return hugePagesInfo, err } numFile := hugepagesDirectory + st.Name() + "/nr_hugepages" val, err := ioutil.ReadFile(numFile) if err != nil { return hugePagesInfo, err } var numPages uint64 // we use sscanf as the file as a new-line that trips up ParseUint // it returns the number of tokens successfully parsed, so if // n != 1, it means we were unable to parse a number from the file n, err := fmt.Sscanf(string(val), "%d", &numPages) if err != nil || n != 1 { return hugePagesInfo, fmt.Errorf("could not parse file %v contents %q", numFile, string(val)) } hugePagesInfo = append(hugePagesInfo, info.HugePagesInfo{ NumPages: numPages, PageSize: pageSize, }) } return hugePagesInfo, nil } func Info(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.MachineInfo, error) { rootFs := "/" if !inHostNamespace { rootFs = "/rootfs" } cpuinfo, err := ioutil.ReadFile(filepath.Join(rootFs, "/proc/cpuinfo")) clockSpeed, err := GetClockSpeed(cpuinfo) if err != nil { return nil, err } memoryCapacity, err := GetMachineMemoryCapacity() if err != nil { return nil, err } hugePagesInfo, err := GetHugePagesInfo() if err != nil { return nil, err } filesystems, err := fsInfo.GetGlobalFsInfo() if err != nil { glog.Errorf("Failed to get global filesystem information: %v", err) } diskMap, err := sysinfo.GetBlockDeviceInfo(sysFs) if err != nil { glog.Errorf("Failed to get disk map: %v", err) } netDevices, err := sysinfo.GetNetworkDevices(sysFs) if err != nil { glog.Errorf("Failed to get network devices: %v", err) } topology, numCores, err := GetTopology(sysFs, string(cpuinfo)) if err != nil { glog.Errorf("Failed to get topology information: %v", err) } systemUUID, err := sysinfo.GetSystemUUID(sysFs) if err != nil { glog.Errorf("Failed to get system UUID: %v", err) } realCloudInfo := cloudinfo.NewRealCloudInfo() cloudProvider := realCloudInfo.GetCloudProvider() instanceType := realCloudInfo.GetInstanceType() instanceID := realCloudInfo.GetInstanceID() machineInfo := &info.MachineInfo{ NumCores: numCores, CpuFrequency: clockSpeed, MemoryCapacity: memoryCapacity, HugePages: hugePagesInfo, DiskMap: diskMap, NetworkDevices: netDevices, Topology: topology, MachineID: getInfoFromFiles(filepath.Join(rootFs, *machineIdFilePath)), SystemUUID: systemUUID, BootID: getInfoFromFiles(filepath.Join(rootFs, *bootIdFilePath)), CloudProvider: cloudProvider, InstanceType: instanceType, InstanceID: instanceID, } for i := range filesystems { fs := filesystems[i] inodes := uint64(0) if fs.Inodes != nil { inodes = *fs.Inodes } machineInfo.Filesystems = append(machineInfo.Filesystems, info.FsInfo{Device: fs.Device, DeviceMajor: uint64(fs.Major), DeviceMinor: uint64(fs.Minor), Type: fs.Type.String(), Capacity: fs.Capacity, Inodes: inodes, HasInodes: fs.Inodes != nil}) } return machineInfo, nil } func ContainerOsVersion() 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 KernelVersion() 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) } cadvisor-0.27.1/machine/machine.go000066400000000000000000000177371315410276000167700ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // The machine package contains functions that extract machine-level specs. package machine import ( "fmt" "io/ioutil" "regexp" "strconv" "strings" // s390/s390x changes "runtime" "syscall" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/utils" "github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/utils/sysinfo" "github.com/golang/glog" ) var ( cpuRegExp = regexp.MustCompile(`^processor\s*:\s*([0-9]+)$`) coreRegExp = regexp.MustCompile(`^core id\s*:\s*([0-9]+)$`) nodeRegExp = regexp.MustCompile(`^physical id\s*:\s*([0-9]+)$`) // Power systems have a different format so cater for both cpuClockSpeedMHz = regexp.MustCompile(`(?:cpu MHz|clock)\s*:\s*([0-9]+\.[0-9]+)(?:MHz)?`) memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`) swapCapacityRegexp = regexp.MustCompile(`SwapTotal:\s*([0-9]+) kB`) ) const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" // GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file. func GetClockSpeed(procInfo []byte) (uint64, error) { // s390/s390x, aarch64 and arm32 changes if isSystemZ() || isAArch64() || isArm32() { return 0, nil } // First look through sys to find a max supported cpu frequency. if utils.FileExists(maxFreqFile) { val, err := ioutil.ReadFile(maxFreqFile) if err != nil { return 0, err } var maxFreq uint64 n, err := fmt.Sscanf(string(val), "%d", &maxFreq) if err != nil || n != 1 { return 0, fmt.Errorf("could not parse frequency %q", val) } return maxFreq, nil } // Fall back to /proc/cpuinfo matches := cpuClockSpeedMHz.FindSubmatch(procInfo) if len(matches) != 2 { return 0, fmt.Errorf("could not detect clock speed from output: %q", string(procInfo)) } speed, err := strconv.ParseFloat(string(matches[1]), 64) if err != nil { return 0, err } // Convert to kHz return uint64(speed * 1000), nil } // GetMachineMemoryCapacity returns the machine's total memory from /proc/meminfo. // Returns the total memory capacity as an uint64 (number of bytes). func GetMachineMemoryCapacity() (uint64, error) { out, err := ioutil.ReadFile("/proc/meminfo") if err != nil { return 0, err } memoryCapacity, err := parseCapacity(out, memoryCapacityRegexp) if err != nil { return 0, err } return memoryCapacity, err } // GetMachineSwapCapacity returns the machine's total swap from /proc/meminfo. // Returns the total swap capacity as an uint64 (number of bytes). func GetMachineSwapCapacity() (uint64, error) { out, err := ioutil.ReadFile("/proc/meminfo") if err != nil { return 0, err } swapCapacity, err := parseCapacity(out, swapCapacityRegexp) if err != nil { return 0, err } return swapCapacity, err } // parseCapacity matches a Regexp in a []byte, returning the resulting value in bytes. // Assumes that the value matched by the Regexp is in KB. func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) { matches := r.FindSubmatch(b) if len(matches) != 2 { return 0, fmt.Errorf("failed to match regexp in output: %q", string(b)) } m, err := strconv.ParseUint(string(matches[1]), 10, 64) if err != nil { return 0, err } // Convert to bytes. return m * 1024, err } func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) { nodes := []info.Node{} // s390/s390x changes if true == isSystemZ() { return nodes, getNumCores(), nil } numCores := 0 lastThread := -1 lastCore := -1 lastNode := -1 for _, line := range strings.Split(cpuinfo, "\n") { if line == "" { continue } ok, val, err := extractValue(line, cpuRegExp) if err != nil { return nil, -1, fmt.Errorf("could not parse cpu info from %q: %v", line, err) } if ok { thread := val numCores++ if lastThread != -1 { // New cpu section. Save last one. nodeIdx, err := addNode(&nodes, lastNode) if err != nil { return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err) } nodes[nodeIdx].AddThread(lastThread, lastCore) lastCore = -1 lastNode = -1 } lastThread = thread continue } ok, val, err = extractValue(line, coreRegExp) if err != nil { return nil, -1, fmt.Errorf("could not parse core info from %q: %v", line, err) } if ok { lastCore = val continue } ok, val, err = extractValue(line, nodeRegExp) if err != nil { return nil, -1, fmt.Errorf("could not parse node info from %q: %v", line, err) } if ok { lastNode = val continue } } nodeIdx, err := addNode(&nodes, lastNode) if err != nil { return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err) } nodes[nodeIdx].AddThread(lastThread, lastCore) if numCores < 1 { return nil, numCores, fmt.Errorf("could not detect any cores") } for idx, node := range nodes { caches, err := sysinfo.GetCacheInfo(sysFs, node.Cores[0].Threads[0]) if err != nil { glog.Errorf("failed to get cache information for node %d: %v", node.Id, err) continue } numThreadsPerCore := len(node.Cores[0].Threads) numThreadsPerNode := len(node.Cores) * numThreadsPerCore for _, cache := range caches { c := info.Cache{ Size: cache.Size, Level: cache.Level, Type: cache.Type, } if cache.Cpus == numThreadsPerNode && cache.Level > 2 { // Add a node-level cache. nodes[idx].AddNodeCache(c) } else if cache.Cpus == numThreadsPerCore { // Add to each core. nodes[idx].AddPerCoreCache(c) } // Ignore unknown caches. } } return nodes, numCores, nil } func extractValue(s string, r *regexp.Regexp) (bool, int, error) { matches := r.FindSubmatch([]byte(s)) if len(matches) == 2 { val, err := strconv.ParseInt(string(matches[1]), 10, 32) if err != nil { return false, -1, err } return true, int(val), nil } return false, -1, nil } func findNode(nodes []info.Node, id int) (bool, int) { for i, n := range nodes { if n.Id == id { return true, i } } return false, -1 } func addNode(nodes *[]info.Node, id int) (int, error) { var idx int if id == -1 { // Some VMs don't fill topology data. Export single package. id = 0 } ok, idx := findNode(*nodes, id) if !ok { // New node node := info.Node{Id: id} // Add per-node memory information. meminfo := fmt.Sprintf("/sys/devices/system/node/node%d/meminfo", id) out, err := ioutil.ReadFile(meminfo) // Ignore if per-node info is not available. if err == nil { m, err := parseCapacity(out, memoryCapacityRegexp) if err != nil { return -1, err } node.Memory = uint64(m) } *nodes = append(*nodes, node) idx = len(*nodes) - 1 } return idx, nil } // s390/s390x changes func getMachineArch() (string, error) { uname := syscall.Utsname{} err := syscall.Uname(&uname) if err != nil { return "", err } var arch string for _, val := range uname.Machine { arch += string(int(val)) } return arch, nil } // arm32 chanes func isArm32() bool { arch, err := getMachineArch() if err == nil { return strings.Contains(arch, "arm") } return false } // aarch64 changes func isAArch64() bool { arch, err := getMachineArch() if err == nil { return strings.Contains(arch, "aarch64") } return false } // s390/s390x changes func isSystemZ() bool { arch, err := getMachineArch() if err == nil { return strings.Contains(arch, "390") } return false } // s390/s390x changes func getNumCores() int { maxProcs := runtime.GOMAXPROCS(0) numCPU := runtime.NumCPU() if maxProcs < numCPU { return maxProcs } return numCPU } cadvisor-0.27.1/machine/testdata/000077500000000000000000000000001315410276000166275ustar00rootroot00000000000000cadvisor-0.27.1/machine/testdata/cpuinfo000066400000000000000000000102611315410276000202150ustar00rootroot00000000000000processor : 0 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 1596.000 cache size : 12288 KB physical id : 0 siblings : 6 core id : 0 cpu cores : 6 apicid : 0 initial apicid : 0 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 1 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 1596.000 cache size : 12288 KB physical id : 0 siblings : 6 core id : 1 cpu cores : 6 apicid : 2 initial apicid : 2 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 2 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 1596.000 cache size : 12288 KB physical id : 0 siblings : 6 core id : 2 cpu cores : 6 apicid : 4 initial apicid : 4 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 3 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 1596.000 cache size : 12288 KB physical id : 1 siblings : 6 core id : 3 cpu cores : 6 apicid : 16 initial apicid : 16 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 4 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 1596.000 cache size : 12288 KB physical id : 1 siblings : 6 core id : 4 cpu cores : 6 apicid : 18 initial apicid : 18 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 5 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 1596.000 cache size : 12288 KB physical id : 1 siblings : 6 core id : 5 cpu cores : 6 apicid : 20 initial apicid : 20 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 6 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 2661.000 cache size : 12288 KB physical id : 0 siblings : 6 core id : 0 cpu cores : 6 apicid : 1 initial apicid : 1 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 7 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 2661.000 cache size : 12288 KB physical id : 0 siblings : 6 core id : 1 cpu cores : 6 apicid : 3 initial apicid : 3 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 8 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 1596.000 cache size : 12288 KB physical id : 0 siblings : 6 core id : 2 cpu cores : 6 apicid : 5 initial apicid : 5 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 9 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 2661.000 cache size : 12288 KB physical id : 1 siblings : 6 core id : 3 cpu cores : 6 apicid : 17 initial apicid : 17 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 10 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 1596.000 cache size : 12288 KB physical id : 1 siblings : 6 core id : 4 cpu cores : 6 apicid : 19 initial apicid : 19 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual processor : 11 cpu family : 6 stepping : 2 microcode : 0x10 cpu MHz : 2661.000 cache size : 12288 KB physical id : 1 siblings : 6 core id : 5 cpu cores : 6 apicid : 21 initial apicid : 21 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes bogomips : 5333.60 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual cadvisor-0.27.1/machine/topology_test.go000066400000000000000000000063671315410276000202740ustar00rootroot00000000000000// 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 machine import ( "io/ioutil" "reflect" "testing" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/utils/sysfs/fakesysfs" ) func TestTopology(t *testing.T) { testfile := "./testdata/cpuinfo" testcpuinfo, err := ioutil.ReadFile(testfile) if err != nil { t.Fatalf("unable to read input test file %s", testfile) } sysFs := &fakesysfs.FakeSysFs{} c := sysfs.CacheInfo{ Size: 32 * 1024, Type: "unified", Level: 1, Cpus: 2, } sysFs.SetCacheInfo(c) topology, numCores, err := GetTopology(sysFs, string(testcpuinfo)) if err != nil { t.Errorf("failed to get topology for sample cpuinfo %s: %v", string(testcpuinfo), err) } if numCores != 12 { t.Errorf("Expected 12 cores, found %d", numCores) } expected_topology := []info.Node{} numNodes := 2 numCoresPerNode := 3 numThreads := 2 cache := info.Cache{ Size: 32 * 1024, Type: "unified", Level: 1, } for i := 0; i < numNodes; i++ { node := info.Node{Id: i} // Copy over Memory from result. TODO(rjnagal): Use memory from fake. node.Memory = topology[i].Memory for j := 0; j < numCoresPerNode; j++ { core := info.Core{Id: i*numCoresPerNode + j} core.Caches = append(core.Caches, cache) for k := 0; k < numThreads; k++ { core.Threads = append(core.Threads, k*numCoresPerNode*numNodes+core.Id) } node.Cores = append(node.Cores, core) } expected_topology = append(expected_topology, node) } if !reflect.DeepEqual(topology, expected_topology) { t.Errorf("Expected topology %+v, got %+v", expected_topology, topology) } } func TestTopologyWithSimpleCpuinfo(t *testing.T) { sysFs := &fakesysfs.FakeSysFs{} c := sysfs.CacheInfo{ Size: 32 * 1024, Type: "unified", Level: 1, Cpus: 1, } sysFs.SetCacheInfo(c) topology, numCores, err := GetTopology(sysFs, "processor\t: 0\n") if err != nil { t.Errorf("Expected cpuinfo with no topology data to succeed.") } node := info.Node{Id: 0} core := info.Core{Id: 0} core.Threads = append(core.Threads, 0) cache := info.Cache{ Size: 32 * 1024, Type: "unified", Level: 1, } core.Caches = append(core.Caches, cache) node.Cores = append(node.Cores, core) // Copy over Memory from result. TODO(rjnagal): Use memory from fake. node.Memory = topology[0].Memory expected := []info.Node{node} if !reflect.DeepEqual(topology, expected) { t.Errorf("Expected topology %+v, got %+v", expected, topology) } if numCores != 1 { t.Errorf("Expected 1 core, found %d", numCores) } } func TestTopologyEmptyCpuinfo(t *testing.T) { _, _, err := GetTopology(&fakesysfs.FakeSysFs{}, "") if err == nil { t.Errorf("Expected empty cpuinfo to fail.") } } cadvisor-0.27.1/manager/000077500000000000000000000000001315410276000150245ustar00rootroot00000000000000cadvisor-0.27.1/manager/container.go000066400000000000000000000434231315410276000173430ustar00rootroot00000000000000// 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 ( "flag" "fmt" "io/ioutil" "math" "math/rand" "os/exec" "path" "regexp" "sort" "strconv" "strings" "sync" "time" "github.com/google/cadvisor/cache/memory" "github.com/google/cadvisor/collector" "github.com/google/cadvisor/container" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/summary" "github.com/google/cadvisor/utils/cpuload" units "github.com/docker/go-units" "github.com/golang/glog" ) // Housekeeping interval. var enableLoadReader = flag.Bool("enable_load_reader", false, "Whether to enable cpu load reader") var HousekeepingInterval = flag.Duration("housekeeping_interval", 1*time.Second, "Interval between container housekeepings") var cgroupPathRegExp = regexp.MustCompile(`devices[^:]*:(.*?)[,;$]`) type containerInfo struct { info.ContainerReference Subcontainers []info.ContainerReference Spec info.ContainerSpec } type containerData struct { handler container.ContainerHandler info containerInfo memoryCache *memory.InMemoryCache lock sync.Mutex loadReader cpuload.CpuLoadReader summaryReader *summary.StatsSummary loadAvg float64 // smoothed load average seen so far. housekeepingInterval time.Duration maxHousekeepingInterval time.Duration allowDynamicHousekeeping bool lastUpdatedTime time.Time lastErrorTime time.Time // Decay value used for load average smoothing. Interval length of 10 seconds is used. loadDecay float64 // Whether to log the usage of this container when it is updated. logUsage bool // Tells the container to stop. stop chan bool // Runs custom metric collectors. collectorManager collector.CollectorManager } // jitter returns a time.Duration between duration and duration + maxFactor * duration, // to allow clients to avoid converging on periodic behavior. If maxFactor is 0.0, a // suggested default value will be chosen. func jitter(duration time.Duration, maxFactor float64) time.Duration { if maxFactor <= 0.0 { maxFactor = 1.0 } wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration)) return wait } func (c *containerData) Start() error { go c.housekeeping() return nil } func (c *containerData) Stop() error { err := c.memoryCache.RemoveContainer(c.info.Name) if err != nil { return err } c.stop <- true return nil } func (c *containerData) allowErrorLogging() bool { if time.Since(c.lastErrorTime) > time.Minute { c.lastErrorTime = time.Now() return true } return false } func (c *containerData) GetInfo(shouldUpdateSubcontainers bool) (*containerInfo, error) { // Get spec and subcontainers. if time.Since(c.lastUpdatedTime) > 5*time.Second { err := c.updateSpec() if err != nil { return nil, err } if shouldUpdateSubcontainers { err = c.updateSubcontainers() if err != nil { return nil, err } } c.lastUpdatedTime = time.Now() } // Make a copy of the info for the user. c.lock.Lock() defer c.lock.Unlock() return &c.info, nil } func (c *containerData) DerivedStats() (v2.DerivedStats, error) { if c.summaryReader == nil { return v2.DerivedStats{}, fmt.Errorf("derived stats not enabled for container %q", c.info.Name) } return c.summaryReader.DerivedStats() } func (c *containerData) getCgroupPath(cgroups string) (string, error) { if cgroups == "-" { return "/", nil } matches := cgroupPathRegExp.FindSubmatch([]byte(cgroups)) if len(matches) != 2 { glog.V(3).Infof("failed to get devices cgroup path from %q", cgroups) // return root in case of failures - devices hierarchy might not be enabled. return "/", nil } return string(matches[1]), nil } // Returns contents of a file inside the container root. // Takes in a path relative to container root. func (c *containerData) ReadFile(filepath string, inHostNamespace bool) ([]byte, error) { pids, err := c.getContainerPids(inHostNamespace) if err != nil { return nil, err } // TODO(rjnagal): Optimize by just reading container's cgroup.proc file when in host namespace. rootfs := "/" if !inHostNamespace { rootfs = "/rootfs" } for _, pid := range pids { filePath := path.Join(rootfs, "/proc", pid, "/root", filepath) glog.V(3).Infof("Trying path %q", filePath) data, err := ioutil.ReadFile(filePath) if err == nil { return data, err } } // No process paths could be found. Declare config non-existent. return nil, fmt.Errorf("file %q does not exist.", filepath) } // Return output for ps command in host /proc with specified format func (c *containerData) getPsOutput(inHostNamespace bool, format string) ([]byte, error) { args := []string{} command := "ps" if !inHostNamespace { command = "/usr/sbin/chroot" args = append(args, "/rootfs", "ps") } args = append(args, "-e", "-o", format) out, err := exec.Command(command, args...).Output() if err != nil { return nil, fmt.Errorf("failed to execute %q command: %v", command, err) } return out, err } // Get pids of processes in this container. // A slightly lighterweight call than GetProcessList if other details are not required. func (c *containerData) getContainerPids(inHostNamespace bool) ([]string, error) { format := "pid,cgroup" out, err := c.getPsOutput(inHostNamespace, format) if err != nil { return nil, err } expectedFields := 2 lines := strings.Split(string(out), "\n") pids := []string{} for _, line := range lines[1:] { if len(line) == 0 { continue } fields := strings.Fields(line) if len(fields) < expectedFields { return nil, fmt.Errorf("expected at least %d fields, found %d: output: %q", expectedFields, len(fields), line) } pid := fields[0] cgroup, err := c.getCgroupPath(fields[1]) if err != nil { return nil, fmt.Errorf("could not parse cgroup path from %q: %v", fields[1], err) } if c.info.Name == cgroup { pids = append(pids, pid) } } return pids, nil } func (c *containerData) GetProcessList(cadvisorContainer string, inHostNamespace bool) ([]v2.ProcessInfo, error) { // report all processes for root. isRoot := c.info.Name == "/" format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm,cgroup" out, err := c.getPsOutput(inHostNamespace, format) if err != nil { return nil, err } expectedFields := 12 processes := []v2.ProcessInfo{} lines := strings.Split(string(out), "\n") for _, line := range lines[1:] { if len(line) == 0 { continue } fields := strings.Fields(line) if len(fields) < expectedFields { return nil, fmt.Errorf("expected at least %d fields, found %d: output: %q", expectedFields, len(fields), line) } pid, err := strconv.Atoi(fields[1]) if err != nil { return nil, fmt.Errorf("invalid pid %q: %v", fields[1], err) } ppid, err := strconv.Atoi(fields[2]) if err != nil { return nil, fmt.Errorf("invalid ppid %q: %v", fields[2], err) } percentCpu, err := strconv.ParseFloat(fields[4], 32) if err != nil { return nil, fmt.Errorf("invalid cpu percent %q: %v", fields[4], err) } percentMem, err := strconv.ParseFloat(fields[5], 32) if err != nil { return nil, fmt.Errorf("invalid memory percent %q: %v", fields[5], err) } rss, err := strconv.ParseUint(fields[6], 0, 64) if err != nil { return nil, fmt.Errorf("invalid rss %q: %v", fields[6], err) } // convert to bytes rss *= 1024 vs, err := strconv.ParseUint(fields[7], 0, 64) if err != nil { return nil, fmt.Errorf("invalid virtual size %q: %v", fields[7], err) } // convert to bytes vs *= 1024 cgroup, err := c.getCgroupPath(fields[11]) if err != nil { return nil, fmt.Errorf("could not parse cgroup path from %q: %v", fields[11], err) } // Remove the ps command we just ran from cadvisor container. // Not necessary, but makes the cadvisor page look cleaner. if !inHostNamespace && cadvisorContainer == cgroup && fields[10] == "ps" { continue } var cgroupPath string if isRoot { cgroupPath = cgroup } if isRoot || c.info.Name == cgroup { processes = append(processes, v2.ProcessInfo{ User: fields[0], Pid: pid, Ppid: ppid, StartTime: fields[3], PercentCpu: float32(percentCpu), PercentMemory: float32(percentMem), RSS: rss, VirtualSize: vs, Status: fields[8], RunningTime: fields[9], Cmd: fields[10], CgroupPath: cgroupPath, }) } } return processes, nil } func newContainerData(containerName string, memoryCache *memory.InMemoryCache, handler container.ContainerHandler, logUsage bool, collectorManager collector.CollectorManager, maxHousekeepingInterval time.Duration, allowDynamicHousekeeping bool) (*containerData, error) { if memoryCache == nil { return nil, fmt.Errorf("nil memory storage") } if handler == nil { return nil, fmt.Errorf("nil container handler") } ref, err := handler.ContainerReference() if err != nil { return nil, err } cont := &containerData{ handler: handler, memoryCache: memoryCache, housekeepingInterval: *HousekeepingInterval, maxHousekeepingInterval: maxHousekeepingInterval, allowDynamicHousekeeping: allowDynamicHousekeeping, logUsage: logUsage, loadAvg: -1.0, // negative value indicates uninitialized. stop: make(chan bool, 1), collectorManager: collectorManager, } cont.info.ContainerReference = ref cont.loadDecay = math.Exp(float64(-cont.housekeepingInterval.Seconds() / 10)) if *enableLoadReader { // Create cpu load reader. loadReader, err := cpuload.New() if err != nil { // TODO(rjnagal): Promote to warning once we support cpu load inside namespaces. glog.Infof("Could not initialize cpu load reader for %q: %s", ref.Name, err) } else { cont.loadReader = loadReader } } err = cont.updateSpec() if err != nil { return nil, err } cont.summaryReader, err = summary.New(cont.info.Spec) if err != nil { cont.summaryReader = nil glog.Warningf("Failed to create summary reader for %q: %v", ref.Name, err) } return cont, nil } // Determine when the next housekeeping should occur. func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Time { if self.allowDynamicHousekeeping { var empty time.Time stats, err := self.memoryCache.RecentStats(self.info.Name, empty, empty, 2) if err != nil { if self.allowErrorLogging() { glog.Warningf("Failed to get RecentStats(%q) while determining the next housekeeping: %v", self.info.Name, err) } } else if len(stats) == 2 { // TODO(vishnuk): Use no processes as a signal. // Raise the interval if usage hasn't changed in the last housekeeping. if stats[0].StatsEq(stats[1]) && (self.housekeepingInterval < self.maxHousekeepingInterval) { self.housekeepingInterval *= 2 if self.housekeepingInterval > self.maxHousekeepingInterval { self.housekeepingInterval = self.maxHousekeepingInterval } } else if self.housekeepingInterval != *HousekeepingInterval { // Lower interval back to the baseline. self.housekeepingInterval = *HousekeepingInterval } } } return lastHousekeeping.Add(jitter(self.housekeepingInterval, 1.0)) } // TODO(vmarmol): Implement stats collecting as a custom collector. func (c *containerData) housekeeping() { // Start any background goroutines - must be cleaned up in c.handler.Cleanup(). c.handler.Start() defer c.handler.Cleanup() // Initialize cpuload reader - must be cleaned up in c.loadReader.Stop() if c.loadReader != nil { err := c.loadReader.Start() if err != nil { glog.Warningf("Could not start cpu load stat collector for %q: %s", c.info.Name, err) } defer c.loadReader.Stop() } // Long housekeeping is either 100ms or half of the housekeeping interval. longHousekeeping := 100 * time.Millisecond if *HousekeepingInterval/2 < longHousekeeping { longHousekeeping = *HousekeepingInterval / 2 } // Housekeep every second. glog.V(3).Infof("Start housekeeping for container %q\n", c.info.Name) lastHousekeeping := time.Now() for { select { case <-c.stop: // Stop housekeeping when signaled. return default: // Perform housekeeping. start := time.Now() c.housekeepingTick() // Log if housekeeping took too long. duration := time.Since(start) if duration >= longHousekeeping { glog.V(3).Infof("[%s] Housekeeping took %s", c.info.Name, duration) } } // Log usage if asked to do so. if c.logUsage { const numSamples = 60 var empty time.Time stats, err := c.memoryCache.RecentStats(c.info.Name, empty, empty, numSamples) if err != nil { if c.allowErrorLogging() { glog.Infof("[%s] Failed to get recent stats for logging usage: %v", c.info.Name, err) } } else if len(stats) < numSamples { // Ignore, not enough stats yet. } else { usageCpuNs := uint64(0) for i := range stats { if i > 0 { usageCpuNs += (stats[i].Cpu.Usage.Total - stats[i-1].Cpu.Usage.Total) } } usageMemory := stats[numSamples-1].Memory.Usage instantUsageInCores := float64(stats[numSamples-1].Cpu.Usage.Total-stats[numSamples-2].Cpu.Usage.Total) / float64(stats[numSamples-1].Timestamp.Sub(stats[numSamples-2].Timestamp).Nanoseconds()) usageInCores := float64(usageCpuNs) / float64(stats[numSamples-1].Timestamp.Sub(stats[0].Timestamp).Nanoseconds()) usageInHuman := units.HumanSize(float64(usageMemory)) glog.Infof("[%s] %.3f cores (average: %.3f cores), %s of memory", c.info.Name, instantUsageInCores, usageInCores, usageInHuman) } } next := c.nextHousekeeping(lastHousekeeping) // Schedule the next housekeeping. Sleep until that time. if time.Now().Before(next) { time.Sleep(next.Sub(time.Now())) } else { next = time.Now() } lastHousekeeping = next } } func (c *containerData) housekeepingTick() { err := c.updateStats() if err != nil { if c.allowErrorLogging() { glog.Infof("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 { // Ignore errors if the container is dead. if !c.handler.Exists() { return nil } return err } customMetrics, err := c.collectorManager.GetSpec() if err != nil { return err } if len(customMetrics) > 0 { spec.HasCustomMetrics = true spec.CustomMetrics = customMetrics } c.lock.Lock() defer c.lock.Unlock() c.info.Spec = spec return nil } // Calculate new smoothed load average using the new sample of runnable threads. // The decay used ensures that the load will stabilize on a new constant value within // 10 seconds. func (c *containerData) updateLoad(newLoad uint64) { if c.loadAvg < 0 { c.loadAvg = float64(newLoad) // initialize to the first seen sample for faster stabilization. } else { c.loadAvg = c.loadAvg*c.loadDecay + float64(newLoad)*(1.0-c.loadDecay) } } func (c *containerData) updateStats() error { stats, statsErr := c.handler.GetStats() if statsErr != nil { // Ignore errors if the container is dead. if !c.handler.Exists() { return nil } // Stats may be partially populated, push those before we return an error. statsErr = fmt.Errorf("%v, continuing to push stats", statsErr) } if stats == nil { return statsErr } if c.loadReader != nil { // TODO(vmarmol): Cache this path. path, err := c.handler.GetCgroupPath("cpu") if err == nil { loadStats, err := c.loadReader.GetCpuLoad(c.info.Name, path) if err != nil { return fmt.Errorf("failed to get load stat for %q - path %q, error %s", c.info.Name, path, err) } stats.TaskStats = loadStats c.updateLoad(loadStats.NrRunning) // convert to 'milliLoad' to avoid floats and preserve precision. stats.Cpu.LoadAverage = int32(c.loadAvg * 1000) } } if c.summaryReader != nil { err := c.summaryReader.AddSample(*stats) if err != nil { // Ignore summary errors for now. glog.V(2).Infof("Failed to add summary stats for %q: %v", c.info.Name, err) } } var customStatsErr error cm := c.collectorManager.(*collector.GenericCollectorManager) if len(cm.Collectors) > 0 { if cm.NextCollectionTime.Before(time.Now()) { customStats, err := c.updateCustomStats() if customStats != nil { stats.CustomMetrics = customStats } if err != nil { customStatsErr = err } } } ref, err := c.handler.ContainerReference() if err != nil { // Ignore errors if the container is dead. if !c.handler.Exists() { return nil } return err } err = c.memoryCache.AddStats(ref, stats) if err != nil { return err } if statsErr != nil { return statsErr } return customStatsErr } func (c *containerData) updateCustomStats() (map[string][]info.MetricVal, error) { _, customStats, customStatsErr := c.collectorManager.Collect() if customStatsErr != nil { if !c.handler.Exists() { return customStats, nil } customStatsErr = fmt.Errorf("%v, continuing to push custom stats", customStatsErr) } return customStats, customStatsErr } func (c *containerData) updateSubcontainers() error { var subcontainers info.ContainerReferenceSlice subcontainers, err := c.handler.ListContainers(container.ListSelf) if err != nil { // Ignore errors if the container is dead. if !c.handler.Exists() { return nil } return err } sort.Sort(subcontainers) c.lock.Lock() defer c.lock.Unlock() c.info.Subcontainers = subcontainers return nil } cadvisor-0.27.1/manager/container_test.go000066400000000000000000000130011315410276000203670ustar00rootroot00000000000000// 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" "reflect" "testing" "time" "github.com/google/cadvisor/cache/memory" "github.com/google/cadvisor/collector" "github.com/google/cadvisor/container" containertest "github.com/google/cadvisor/container/testing" info "github.com/google/cadvisor/info/v1" itest "github.com/google/cadvisor/info/v1/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const containerName = "/container" // Create a containerData instance for a test. func setupContainerData(t *testing.T, spec info.ContainerSpec) (*containerData, *containertest.MockContainerHandler, *memory.InMemoryCache) { mockHandler := containertest.NewMockContainerHandler(containerName) mockHandler.On("GetSpec").Return( spec, nil, ) memoryCache := memory.New(60, nil) ret, err := newContainerData(containerName, memoryCache, mockHandler, false, &collector.GenericCollectorManager{}, 60*time.Second, true) if err != nil { t.Fatal(err) } return ret, mockHandler, memoryCache } // Create a containerData instance for a test and add a default GetSpec mock. func newTestContainerData(t *testing.T) (*containerData, *containertest.MockContainerHandler, *memory.InMemoryCache) { spec := itest.GenerateRandomContainerSpec(4) ret, mockHandler, memoryCache := setupContainerData(t, spec) return ret, mockHandler, memoryCache } func TestUpdateSubcontainers(t *testing.T) { subcontainers := []info.ContainerReference{ {Name: "/container/ee0103"}, {Name: "/container/abcd"}, {Name: "/container/something"}, } cd, mockHandler, _ := newTestContainerData(t) mockHandler.On("ListContainers", container.ListSelf).Return( subcontainers, nil, ) err := cd.updateSubcontainers() if err != nil { t.Fatal(err) } if len(cd.info.Subcontainers) != len(subcontainers) { t.Errorf("Received %v subcontainers, should be %v", len(cd.info.Subcontainers), len(subcontainers)) } for _, sub := range cd.info.Subcontainers { found := false for _, sub2 := range subcontainers { if sub.Name == sub2.Name { found = true } } if !found { t.Errorf("Received unknown sub container %v", sub) } } mockHandler.AssertExpectations(t) } func TestUpdateSubcontainersWithError(t *testing.T) { cd, mockHandler, _ := newTestContainerData(t) mockHandler.On("ListContainers", container.ListSelf).Return( []info.ContainerReference{}, fmt.Errorf("some error"), ) mockHandler.On("Exists").Return(true) assert.NotNil(t, cd.updateSubcontainers()) assert.Empty(t, cd.info.Subcontainers, "subcontainers should not be populated on failure") mockHandler.AssertExpectations(t) } func TestUpdateSubcontainersWithErrorOnDeadContainer(t *testing.T) { cd, mockHandler, _ := newTestContainerData(t) mockHandler.On("ListContainers", container.ListSelf).Return( []info.ContainerReference{}, fmt.Errorf("some error"), ) mockHandler.On("Exists").Return(false) assert.Nil(t, cd.updateSubcontainers()) mockHandler.AssertExpectations(t) } func checkNumStats(t *testing.T, memoryCache *memory.InMemoryCache, numStats int) { var empty time.Time stats, err := memoryCache.RecentStats(containerName, empty, empty, -1) require.Nil(t, err) assert.Len(t, stats, numStats) } func TestUpdateStats(t *testing.T) { statsList := itest.GenerateRandomStats(1, 4, 1*time.Second) stats := statsList[0] cd, mockHandler, memoryCache := newTestContainerData(t) mockHandler.On("GetStats").Return( stats, nil, ) err := cd.updateStats() if err != nil { t.Fatal(err) } checkNumStats(t, memoryCache, 1) mockHandler.AssertExpectations(t) } func TestUpdateSpec(t *testing.T) { spec := itest.GenerateRandomContainerSpec(4) cd, mockHandler, _ := newTestContainerData(t) mockHandler.On("GetSpec").Return( spec, nil, ) err := cd.updateSpec() if err != nil { t.Fatal(err) } mockHandler.AssertExpectations(t) } func TestGetInfo(t *testing.T) { spec := itest.GenerateRandomContainerSpec(4) subcontainers := []info.ContainerReference{ {Name: "/container/ee0103"}, {Name: "/container/abcd"}, {Name: "/container/something"}, } cd, mockHandler, _ := setupContainerData(t, spec) mockHandler.On("ListContainers", container.ListSelf).Return( subcontainers, nil, ) mockHandler.Aliases = []string{"a1", "a2"} info, err := cd.GetInfo(true) if err != nil { t.Fatal(err) } mockHandler.AssertExpectations(t) if len(info.Subcontainers) != len(subcontainers) { t.Errorf("Received %v subcontainers, should be %v", len(info.Subcontainers), len(subcontainers)) } for _, sub := range info.Subcontainers { found := false for _, sub2 := range subcontainers { if sub.Name == sub2.Name { found = true } } if !found { t.Errorf("Received unknown sub container %v", sub) } } if !reflect.DeepEqual(spec, info.Spec) { t.Errorf("received wrong container spec") } if info.Name != mockHandler.Name { t.Errorf("received wrong container name: received %v; should be %v", info.Name, mockHandler.Name) } } cadvisor-0.27.1/manager/manager.go000066400000000000000000001205531315410276000167730ustar00rootroot00000000000000// 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. // Manager of cAdvisor-monitored containers. package manager import ( "flag" "fmt" "os" "path" "strconv" "strings" "sync" "time" "github.com/google/cadvisor/cache/memory" "github.com/google/cadvisor/collector" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/crio" "github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/container/raw" "github.com/google/cadvisor/container/rkt" "github.com/google/cadvisor/container/systemd" "github.com/google/cadvisor/events" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/machine" "github.com/google/cadvisor/manager/watcher" rawwatcher "github.com/google/cadvisor/manager/watcher/raw" rktwatcher "github.com/google/cadvisor/manager/watcher/rkt" "github.com/google/cadvisor/utils/oomparser" "github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/version" "net/http" "github.com/golang/glog" "github.com/opencontainers/runc/libcontainer/cgroups" ) var globalHousekeepingInterval = flag.Duration("global_housekeeping_interval", 1*time.Minute, "Interval between global housekeepings") var logCadvisorUsage = flag.Bool("log_cadvisor_usage", false, "Whether to log the usage of the cAdvisor container") var eventStorageAgeLimit = flag.String("event_storage_age_limit", "default=24h", "Max length of time for which to store events (per type). Value is a comma separated list of key values, where the keys are event types (e.g.: creation, oom) or \"default\" and the value is a duration. Default is applied to all non-specified event types") var eventStorageEventLimit = flag.String("event_storage_event_limit", "default=100000", "Max number of events to store (per type). Value is a comma separated list of key values, where the keys are event types (e.g.: creation, oom) or \"default\" and the value is an integer. Default is applied to all non-specified event types") var applicationMetricsCountLimit = flag.Int("application_metrics_count_limit", 100, "Max number of application metrics to store (per container)") // The Manager interface defines operations for starting a manager and getting // container and machine information. type Manager interface { // Start the manager. Calling other manager methods before this returns // may produce undefined behavior. Start() error // Stops the manager. Stop() error // information about a container. GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) // Get V2 information about a container. // Recursive (subcontainer) requests are best-effort, and may return a partial result alongside an // error in the partial failure case. GetContainerInfoV2(containerName string, options v2.RequestOptions) (map[string]v2.ContainerInfo, error) // Get information about all subcontainers of the specified container (includes self). SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) // Gets all the Docker containers. Return is a map from full container name to ContainerInfo. AllDockerContainers(query *info.ContainerInfoRequest) (map[string]info.ContainerInfo, error) // Gets information about a specific Docker container. The specified name is within the Docker namespace. DockerContainer(dockerName string, query *info.ContainerInfoRequest) (info.ContainerInfo, error) // Gets spec for all containers based on request options. GetContainerSpec(containerName string, options v2.RequestOptions) (map[string]v2.ContainerSpec, error) // Gets summary stats for all containers based on request options. GetDerivedStats(containerName string, options v2.RequestOptions) (map[string]v2.DerivedStats, error) // Get info for all requested containers based on the request options. GetRequestedContainersInfo(containerName string, options v2.RequestOptions) (map[string]*info.ContainerInfo, error) // Returns true if the named container exists. Exists(containerName string) bool // Get information about the machine. GetMachineInfo() (*info.MachineInfo, error) // Get version information about different components we depend on. GetVersionInfo() (*info.VersionInfo, error) // GetFsInfoByFsUUID returns the information of the device having the // specified filesystem uuid. If no such device with the UUID exists, this // function will return the fs.ErrNoSuchDevice error. GetFsInfoByFsUUID(uuid string) (v2.FsInfo, error) // Get filesystem information for the filesystem that contains the given directory GetDirFsInfo(dir string) (v2.FsInfo, error) // Get filesystem information for a given label. // Returns information for all global filesystems if label is empty. GetFsInfo(label string) ([]v2.FsInfo, error) // Get ps output for a container. GetProcessList(containerName string, options v2.RequestOptions) ([]v2.ProcessInfo, error) // Get events streamed through passedChannel that fit the request. WatchForEvents(request *events.Request) (*events.EventChannel, error) // Get past events that have been detected and that fit the request. GetPastEvents(request *events.Request) ([]*info.Event, error) CloseEventChannel(watch_id int) // Get status information about docker. DockerInfo() (info.DockerStatus, error) // Get details about interesting docker images. DockerImages() ([]info.DockerImage, error) // Returns debugging information. Map of lines per category. DebugInfo() map[string][]string } // New takes a memory storage and returns a new manager. func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingInterval time.Duration, allowDynamicHousekeeping bool, ignoreMetricsSet container.MetricSet, collectorHttpClient *http.Client) (Manager, error) { if memoryCache == nil { return nil, fmt.Errorf("manager requires memory storage") } // Detect the container we are running on. selfContainer, err := cgroups.GetOwnCgroupPath("cpu") if err != nil { return nil, err } glog.Infof("cAdvisor running in container: %q", selfContainer) dockerStatus, err := docker.Status() if err != nil { glog.Warningf("Unable to connect to Docker: %v", err) } rktPath, err := rkt.RktPath() if err != nil { glog.Warningf("unable to connect to Rkt api service: %v", err) } crioClient, err := crio.Client() if err != nil { return nil, err } crioInfo, err := crioClient.Info() if err != nil { glog.Warningf("unable to connect to CRI-O api service: %v", err) } context := fs.Context{ Docker: fs.DockerContext{ Root: docker.RootDir(), Driver: dockerStatus.Driver, DriverStatus: dockerStatus.DriverStatus, }, RktPath: rktPath, Crio: fs.CrioContext{ Root: crioInfo.StorageRoot, }, } fsInfo, err := fs.NewFsInfo(context) if err != nil { return nil, err } // If cAdvisor was started with host's rootfs mounted, assume that its running // in its own namespaces. inHostNamespace := false if _, err := os.Stat("/rootfs/proc"); os.IsNotExist(err) { inHostNamespace = true } // Register for new subcontainers. eventsChannel := make(chan watcher.ContainerEvent, 16) newManager := &manager{ containers: make(map[namespacedContainerName]*containerData), quitChannels: make([]chan error, 0, 2), memoryCache: memoryCache, fsInfo: fsInfo, cadvisorContainer: selfContainer, inHostNamespace: inHostNamespace, startupTime: time.Now(), maxHousekeepingInterval: maxHousekeepingInterval, allowDynamicHousekeeping: allowDynamicHousekeeping, ignoreMetrics: ignoreMetricsSet, containerWatchers: []watcher.ContainerWatcher{}, eventsChannel: eventsChannel, collectorHttpClient: collectorHttpClient, } machineInfo, err := machine.Info(sysfs, fsInfo, inHostNamespace) if err != nil { return nil, err } newManager.machineInfo = *machineInfo glog.Infof("Machine: %+v", newManager.machineInfo) versionInfo, err := getVersionInfo() if err != nil { return nil, err } glog.Infof("Version: %+v", *versionInfo) newManager.eventHandler = events.NewEventManager(parseEventsStoragePolicy()) return newManager, nil } // A namespaced container name. type namespacedContainerName struct { // The namespace of the container. Can be empty for the root namespace. Namespace string // The name of the container in this namespace. Name string } type manager struct { containers map[namespacedContainerName]*containerData containersLock sync.RWMutex memoryCache *memory.InMemoryCache fsInfo fs.FsInfo machineInfo info.MachineInfo quitChannels []chan error cadvisorContainer string inHostNamespace bool eventHandler events.EventManager startupTime time.Time maxHousekeepingInterval time.Duration allowDynamicHousekeeping bool ignoreMetrics container.MetricSet containerWatchers []watcher.ContainerWatcher eventsChannel chan watcher.ContainerEvent collectorHttpClient *http.Client } // Start the container manager. func (self *manager) Start() error { err := docker.Register(self, self.fsInfo, self.ignoreMetrics) if err != nil { glog.Warningf("Docker container factory registration failed: %v.", err) } err = rkt.Register(self, self.fsInfo, self.ignoreMetrics) if err != nil { glog.Warningf("Registration of the rkt container factory failed: %v", err) } else { watcher, err := rktwatcher.NewRktContainerWatcher() if err != nil { return err } self.containerWatchers = append(self.containerWatchers, watcher) } err = crio.Register(self, self.fsInfo, self.ignoreMetrics) if err != nil { glog.Warningf("Registration of the crio container factory failed: %v", err) } err = systemd.Register(self, self.fsInfo, self.ignoreMetrics) if err != nil { glog.Warningf("Registration of the systemd container factory failed: %v", err) } err = raw.Register(self, self.fsInfo, self.ignoreMetrics) if err != nil { glog.Errorf("Registration of the raw container factory failed: %v", err) } rawWatcher, err := rawwatcher.NewRawContainerWatcher() if err != nil { return err } self.containerWatchers = append(self.containerWatchers, rawWatcher) // Watch for OOMs. err = self.watchForNewOoms() if err != nil { glog.Warningf("Could not configure a source for OOM detection, disabling OOM events: %v", err) } // If there are no factories, don't start any housekeeping and serve the information we do have. if !container.HasFactories() { return nil } // Create root and then recover all containers. err = self.createContainer("/", watcher.Raw) if err != nil { return err } glog.Infof("Starting recovery of all containers") err = self.detectSubcontainers("/") if err != nil { return err } glog.Infof("Recovery completed") // Watch for new container. quitWatcher := make(chan error) err = self.watchForNewContainers(quitWatcher) if err != nil { return err } self.quitChannels = append(self.quitChannels, quitWatcher) // Look for new containers in the main housekeeping thread. quitGlobalHousekeeping := make(chan error) self.quitChannels = append(self.quitChannels, quitGlobalHousekeeping) go self.globalHousekeeping(quitGlobalHousekeeping) return nil } func (self *manager) Stop() error { // Stop and wait on all quit channels. for i, c := range self.quitChannels { // Send the exit signal and wait on the thread to exit (by closing the channel). c <- nil err := <-c if err != nil { // Remove the channels that quit successfully. self.quitChannels = self.quitChannels[i:] return err } } self.quitChannels = make([]chan error, 0, 2) return nil } func (self *manager) globalHousekeeping(quit chan error) { // Long housekeeping is either 100ms or half of the housekeeping interval. longHousekeeping := 100 * time.Millisecond if *globalHousekeepingInterval/2 < longHousekeeping { longHousekeeping = *globalHousekeepingInterval / 2 } ticker := time.Tick(*globalHousekeepingInterval) for { select { case t := <-ticker: start := time.Now() // Check for new containers. err := self.detectSubcontainers("/") if err != nil { glog.Errorf("Failed to detect containers: %s", err) } // Log if housekeeping took too long. duration := time.Since(start) if duration >= longHousekeeping { glog.V(3).Infof("Global Housekeeping(%d) took %s", t.Unix(), duration) } case <-quit: // Quit if asked to do so. quit <- nil glog.Infof("Exiting global housekeeping thread") return } } } func (self *manager) getContainerData(containerName string) (*containerData, error) { var cont *containerData var ok bool func() { self.containersLock.RLock() defer self.containersLock.RUnlock() // Ensure we have the container. cont, ok = self.containers[namespacedContainerName{ Name: containerName, }] }() if !ok { return nil, fmt.Errorf("unknown container %q", containerName) } return cont, nil } func (self *manager) GetDerivedStats(containerName string, options v2.RequestOptions) (map[string]v2.DerivedStats, error) { conts, err := self.getRequestedContainers(containerName, options) if err != nil { return nil, err } var errs partialFailure stats := make(map[string]v2.DerivedStats) for name, cont := range conts { d, err := cont.DerivedStats() if err != nil { errs.append(name, "DerivedStats", err) } stats[name] = d } return stats, errs.OrNil() } func (self *manager) GetContainerSpec(containerName string, options v2.RequestOptions) (map[string]v2.ContainerSpec, error) { conts, err := self.getRequestedContainers(containerName, options) if err != nil { return nil, err } var errs partialFailure specs := make(map[string]v2.ContainerSpec) for name, cont := range conts { cinfo, err := cont.GetInfo(false) if err != nil { errs.append(name, "GetInfo", err) } spec := self.getV2Spec(cinfo) specs[name] = spec } return specs, errs.OrNil() } // Get V2 container spec from v1 container info. func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec { spec := self.getAdjustedSpec(cinfo) return v2.ContainerSpecFromV1(&spec, cinfo.Aliases, cinfo.Namespace) } func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec { spec := cinfo.Spec // Set default value to an actual value if spec.HasMemory { // Memory.Limit is 0 means there's no limit if spec.Memory.Limit == 0 { spec.Memory.Limit = uint64(self.machineInfo.MemoryCapacity) } } return spec } func (self *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) { cont, err := self.getContainerData(containerName) if err != nil { return nil, err } return self.containerDataToContainerInfo(cont, query) } func (self *manager) GetContainerInfoV2(containerName string, options v2.RequestOptions) (map[string]v2.ContainerInfo, error) { containers, err := self.getRequestedContainers(containerName, options) if err != nil { return nil, err } var errs partialFailure var nilTime time.Time // Ignored. infos := make(map[string]v2.ContainerInfo, len(containers)) for name, container := range containers { result := v2.ContainerInfo{} cinfo, err := container.GetInfo(false) if err != nil { errs.append(name, "GetInfo", err) infos[name] = result continue } result.Spec = self.getV2Spec(cinfo) stats, err := self.memoryCache.RecentStats(name, nilTime, nilTime, options.Count) if err != nil { errs.append(name, "RecentStats", err) infos[name] = result continue } result.Stats = v2.ContainerStatsFromV1(containerName, &cinfo.Spec, stats) infos[name] = result } return infos, errs.OrNil() } func (self *manager) containerDataToContainerInfo(cont *containerData, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) { // Get the info from the container. cinfo, err := cont.GetInfo(true) if err != nil { return nil, err } stats, err := self.memoryCache.RecentStats(cinfo.Name, query.Start, query.End, query.NumStats) if err != nil { return nil, err } // Make a copy of the info for the user. ret := &info.ContainerInfo{ ContainerReference: cinfo.ContainerReference, Subcontainers: cinfo.Subcontainers, Spec: self.getAdjustedSpec(cinfo), Stats: stats, } return ret, nil } func (self *manager) getContainer(containerName string) (*containerData, error) { self.containersLock.RLock() defer self.containersLock.RUnlock() cont, ok := self.containers[namespacedContainerName{Name: containerName}] if !ok { return nil, fmt.Errorf("unknown container %q", containerName) } return cont, nil } func (self *manager) getSubcontainers(containerName string) map[string]*containerData { self.containersLock.RLock() defer self.containersLock.RUnlock() containersMap := make(map[string]*containerData, len(self.containers)) // Get all the unique subcontainers of the specified container matchedName := path.Join(containerName, "/") for i := range self.containers { name := self.containers[i].info.Name if name == containerName || strings.HasPrefix(name, matchedName) { containersMap[self.containers[i].info.Name] = self.containers[i] } } return containersMap } func (self *manager) SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { containersMap := self.getSubcontainers(containerName) containers := make([]*containerData, 0, len(containersMap)) for _, cont := range containersMap { containers = append(containers, cont) } return self.containerDataSliceToContainerInfoSlice(containers, query) } func (self *manager) getAllDockerContainers() map[string]*containerData { self.containersLock.RLock() defer self.containersLock.RUnlock() containers := make(map[string]*containerData, len(self.containers)) // Get containers in the Docker namespace. for name, cont := range self.containers { if name.Namespace == docker.DockerNamespace { containers[cont.info.Name] = cont } } return containers } func (self *manager) AllDockerContainers(query *info.ContainerInfoRequest) (map[string]info.ContainerInfo, error) { containers := self.getAllDockerContainers() output := make(map[string]info.ContainerInfo, len(containers)) for name, cont := range containers { inf, err := self.containerDataToContainerInfo(cont, query) if err != nil { return nil, err } output[name] = *inf } return output, nil } func (self *manager) getDockerContainer(containerName string) (*containerData, error) { self.containersLock.RLock() defer self.containersLock.RUnlock() // Check for the container in the Docker container namespace. cont, ok := self.containers[namespacedContainerName{ Namespace: docker.DockerNamespace, Name: containerName, }] // Look for container by short prefix name if no exact match found. if !ok { for contName, c := range self.containers { if contName.Namespace == docker.DockerNamespace && strings.HasPrefix(contName.Name, containerName) { if cont == nil { cont = c } else { return nil, fmt.Errorf("unable to find container. Container %q is not unique", containerName) } } } if cont == nil { return nil, fmt.Errorf("unable to find Docker container %q", containerName) } } return cont, nil } func (self *manager) DockerContainer(containerName string, query *info.ContainerInfoRequest) (info.ContainerInfo, error) { container, err := self.getDockerContainer(containerName) if err != nil { return info.ContainerInfo{}, err } inf, err := self.containerDataToContainerInfo(container, query) if err != nil { return info.ContainerInfo{}, err } return *inf, nil } func (self *manager) containerDataSliceToContainerInfoSlice(containers []*containerData, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { if len(containers) == 0 { return nil, fmt.Errorf("no containers found") } // Get the info for each container. output := make([]*info.ContainerInfo, 0, len(containers)) for i := range containers { cinfo, err := self.containerDataToContainerInfo(containers[i], query) if err != nil { // Skip containers with errors, we try to degrade gracefully. continue } output = append(output, cinfo) } return output, nil } func (self *manager) GetRequestedContainersInfo(containerName string, options v2.RequestOptions) (map[string]*info.ContainerInfo, error) { containers, err := self.getRequestedContainers(containerName, options) if err != nil { return nil, err } var errs partialFailure containersMap := make(map[string]*info.ContainerInfo) query := info.ContainerInfoRequest{ NumStats: options.Count, } for name, data := range containers { info, err := self.containerDataToContainerInfo(data, &query) if err != nil { errs.append(name, "containerDataToContainerInfo", err) } containersMap[name] = info } return containersMap, errs.OrNil() } func (self *manager) getRequestedContainers(containerName string, options v2.RequestOptions) (map[string]*containerData, error) { containersMap := make(map[string]*containerData) switch options.IdType { case v2.TypeName: if options.Recursive == false { cont, err := self.getContainer(containerName) if err != nil { return containersMap, err } containersMap[cont.info.Name] = cont } else { containersMap = self.getSubcontainers(containerName) if len(containersMap) == 0 { return containersMap, fmt.Errorf("unknown container: %q", containerName) } } case v2.TypeDocker: if options.Recursive == false { containerName = strings.TrimPrefix(containerName, "/") cont, err := self.getDockerContainer(containerName) if err != nil { return containersMap, err } containersMap[cont.info.Name] = cont } else { if containerName != "/" { return containersMap, fmt.Errorf("invalid request for docker container %q with subcontainers", containerName) } containersMap = self.getAllDockerContainers() } default: return containersMap, fmt.Errorf("invalid request type %q", options.IdType) } return containersMap, nil } func (self *manager) GetDirFsInfo(dir string) (v2.FsInfo, error) { device, err := self.fsInfo.GetDirFsDevice(dir) if err != nil { return v2.FsInfo{}, fmt.Errorf("failed to get device for dir %q: %v", dir, err) } return self.getFsInfoByDeviceName(device.Device) } func (self *manager) GetFsInfoByFsUUID(uuid string) (v2.FsInfo, error) { device, err := self.fsInfo.GetDeviceInfoByFsUUID(uuid) if err != nil { return v2.FsInfo{}, err } return self.getFsInfoByDeviceName(device.Device) } func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) { var empty time.Time // Get latest data from filesystems hanging off root container. stats, err := self.memoryCache.RecentStats("/", empty, empty, 1) if err != nil { return nil, err } dev := "" if len(label) != 0 { dev, err = self.fsInfo.GetDeviceForLabel(label) if err != nil { return nil, err } } fsInfo := []v2.FsInfo{} for i := range stats[0].Filesystem { fs := stats[0].Filesystem[i] if len(label) != 0 && fs.Device != dev { continue } mountpoint, err := self.fsInfo.GetMountpointForDevice(fs.Device) if err != nil { return nil, err } labels, err := self.fsInfo.GetLabelsForDevice(fs.Device) if err != nil { return nil, err } fi := v2.FsInfo{ Timestamp: stats[0].Timestamp, Device: fs.Device, Mountpoint: mountpoint, Capacity: fs.Limit, Usage: fs.Usage, Available: fs.Available, Labels: labels, } if fs.HasInodes { fi.Inodes = &fs.Inodes fi.InodesFree = &fs.InodesFree } fsInfo = append(fsInfo, fi) } return fsInfo, nil } func (m *manager) GetMachineInfo() (*info.MachineInfo, error) { // Copy and return the MachineInfo. return &m.machineInfo, nil } func (m *manager) GetVersionInfo() (*info.VersionInfo, error) { // TODO: Consider caching this and periodically updating. The VersionInfo may change if // the docker daemon is started after the cAdvisor client is created. Caching the value // would be helpful so we would be able to return the last known docker version if // docker was down at the time of a query. return getVersionInfo() } func (m *manager) Exists(containerName string) bool { m.containersLock.Lock() defer m.containersLock.Unlock() namespacedName := namespacedContainerName{ Name: containerName, } _, ok := m.containers[namespacedName] if ok { return true } return false } func (m *manager) GetProcessList(containerName string, options v2.RequestOptions) ([]v2.ProcessInfo, error) { // override recursive. Only support single container listing. options.Recursive = false conts, err := m.getRequestedContainers(containerName, options) if err != nil { return nil, err } if len(conts) != 1 { return nil, fmt.Errorf("Expected the request to match only one container") } // TODO(rjnagal): handle count? Only if we can do count by type (eg. top 5 cpu users) ps := []v2.ProcessInfo{} for _, cont := range conts { ps, err = cont.GetProcessList(m.cadvisorContainer, m.inHostNamespace) if err != nil { return nil, err } } return ps, nil } func (m *manager) registerCollectors(collectorConfigs map[string]string, cont *containerData) error { for k, v := range collectorConfigs { configFile, err := cont.ReadFile(v, m.inHostNamespace) if err != nil { return fmt.Errorf("failed to read config file %q for config %q, container %q: %v", k, v, cont.info.Name, err) } glog.V(3).Infof("Got config from %q: %q", v, configFile) if strings.HasPrefix(k, "prometheus") || strings.HasPrefix(k, "Prometheus") { newCollector, err := collector.NewPrometheusCollector(k, configFile, *applicationMetricsCountLimit, cont.handler, m.collectorHttpClient) if err != nil { glog.Infof("failed to create collector for container %q, config %q: %v", cont.info.Name, k, err) return err } err = cont.collectorManager.RegisterCollector(newCollector) if err != nil { glog.Infof("failed to register collector for container %q, config %q: %v", cont.info.Name, k, err) return err } } else { newCollector, err := collector.NewCollector(k, configFile, *applicationMetricsCountLimit, cont.handler, m.collectorHttpClient) if err != nil { glog.Infof("failed to create collector for container %q, config %q: %v", cont.info.Name, k, err) return err } err = cont.collectorManager.RegisterCollector(newCollector) if err != nil { glog.Infof("failed to register collector for container %q, config %q: %v", cont.info.Name, k, err) return err } } } return nil } // Enables overwriting an existing containerData/Handler object for a given containerName. // Can't use createContainer as it just returns if a given containerName has a handler already. // Ex: rkt handler will want to take priority over the raw handler, but the raw handler might be created first. // Only allow raw handler to be overridden func (m *manager) overrideContainer(containerName string, watchSource watcher.ContainerWatchSource) error { m.containersLock.Lock() defer m.containersLock.Unlock() namespacedName := namespacedContainerName{ Name: containerName, } if _, ok := m.containers[namespacedName]; ok { containerData := m.containers[namespacedName] if containerData.handler.Type() != container.ContainerTypeRaw { return nil } err := m.destroyContainerLocked(containerName) if err != nil { return fmt.Errorf("overrideContainer: failed to destroy containerData/handler for %v: %v", containerName, err) } } return m.createContainerLocked(containerName, watchSource) } // Create a container. func (m *manager) createContainer(containerName string, watchSource watcher.ContainerWatchSource) error { m.containersLock.Lock() defer m.containersLock.Unlock() return m.createContainerLocked(containerName, watchSource) } func (m *manager) createContainerLocked(containerName string, watchSource watcher.ContainerWatchSource) error { namespacedName := namespacedContainerName{ Name: containerName, } // Check that the container didn't already exist. if _, ok := m.containers[namespacedName]; ok { return nil } handler, accept, err := container.NewContainerHandler(containerName, watchSource, m.inHostNamespace) if err != nil { return err } if !accept { // ignoring this container. glog.V(4).Infof("ignoring container %q", containerName) return nil } collectorManager, err := collector.NewCollectorManager() if err != nil { return err } logUsage := *logCadvisorUsage && containerName == m.cadvisorContainer cont, err := newContainerData(containerName, m.memoryCache, handler, logUsage, collectorManager, m.maxHousekeepingInterval, m.allowDynamicHousekeeping) if err != nil { return err } // Add collectors labels := handler.GetContainerLabels() collectorConfigs := collector.GetCollectorConfigs(labels) err = m.registerCollectors(collectorConfigs, cont) if err != nil { glog.Infof("failed to register collectors for %q: %v", containerName, err) } // Add the container name and all its aliases. The aliases must be within the namespace of the factory. m.containers[namespacedName] = cont for _, alias := range cont.info.Aliases { m.containers[namespacedContainerName{ Namespace: cont.info.Namespace, Name: alias, }] = cont } glog.V(3).Infof("Added container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace) contSpec, err := cont.handler.GetSpec() if err != nil { return err } contRef, err := cont.handler.ContainerReference() if err != nil { return err } newEvent := &info.Event{ ContainerName: contRef.Name, Timestamp: contSpec.CreationTime, EventType: info.EventContainerCreation, } err = m.eventHandler.AddEvent(newEvent) if err != nil { return err } // Start the container's housekeeping. return cont.Start() } func (m *manager) destroyContainer(containerName string) error { m.containersLock.Lock() defer m.containersLock.Unlock() return m.destroyContainerLocked(containerName) } func (m *manager) destroyContainerLocked(containerName string) error { namespacedName := namespacedContainerName{ Name: containerName, } cont, ok := m.containers[namespacedName] if !ok { // Already destroyed, done. return nil } // 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, namespacedName) for _, alias := range cont.info.Aliases { delete(m.containers, namespacedContainerName{ Namespace: cont.info.Namespace, Name: alias, }) } glog.V(3).Infof("Destroyed container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace) contRef, err := cont.handler.ContainerReference() if err != nil { return err } newEvent := &info.Event{ ContainerName: contRef.Name, Timestamp: time.Now(), EventType: info.EventContainerDeletion, } err = m.eventHandler.AddEvent(newEvent) if err != nil { return err } return nil } // Detect all containers that have been added or deleted from the specified container. func (m *manager) getContainersDiff(containerName string) (added []info.ContainerReference, removed []info.ContainerReference, err error) { m.containersLock.RLock() defer m.containersLock.RUnlock() // Get all subcontainers recursively. cont, ok := m.containers[namespacedContainerName{ Name: containerName, }] if !ok { return nil, nil, fmt.Errorf("failed to find container %q while checking for new containers", containerName) } allContainers, err := cont.handler.ListContainers(container.ListRecursive) if err != nil { return nil, nil, err } allContainers = append(allContainers, info.ContainerReference{Name: containerName}) // 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.Name { allContainersSet[name.Name] = d } } // Added containers for _, c := range allContainers { delete(allContainersSet, c.Name) _, ok := m.containers[namespacedContainerName{ Name: 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 subcontainers and reflect the setup here. func (m *manager) detectSubcontainers(containerName string) error { added, removed, err := m.getContainersDiff(containerName) if err != nil { return err } // Add the new containers. for _, cont := range added { err = m.createContainer(cont.Name, watcher.Raw) if err != nil { glog.Errorf("Failed to create existing container: %s: %s", cont.Name, err) } } // Remove the old containers. for _, cont := range removed { err = m.destroyContainer(cont.Name) if err != nil { glog.Errorf("Failed to destroy existing container: %s: %s", cont.Name, err) } } return nil } // Watches for new containers started in the system. Runs forever unless there is a setup error. func (self *manager) watchForNewContainers(quit chan error) error { for _, watcher := range self.containerWatchers { err := watcher.Start(self.eventsChannel) if err != nil { return err } } // There is a race between starting the watch and new container creation so we do a detection before we read new containers. err := self.detectSubcontainers("/") if err != nil { return err } // Listen to events from the container handler. go func() { for { select { case event := <-self.eventsChannel: switch { case event.EventType == watcher.ContainerAdd: switch event.WatchSource { // the Rkt and Raw watchers can race, and if Raw wins, we want Rkt to override and create a new handler for Rkt containers case watcher.Rkt: err = self.overrideContainer(event.Name, event.WatchSource) default: err = self.createContainer(event.Name, event.WatchSource) } case event.EventType == watcher.ContainerDelete: err = self.destroyContainer(event.Name) } if err != nil { glog.Warningf("Failed to process watch event %+v: %v", event, err) } case <-quit: var errs partialFailure // Stop processing events if asked to quit. for i, watcher := range self.containerWatchers { err := watcher.Stop() if err != nil { errs.append(fmt.Sprintf("watcher %d", i), "Stop", err) } } if len(errs) > 0 { quit <- errs } else { quit <- nil glog.Infof("Exiting thread watching subcontainers") return } } } }() return nil } func (self *manager) watchForNewOoms() error { glog.Infof("Started watching for new ooms in manager") outStream := make(chan *oomparser.OomInstance, 10) oomLog, err := oomparser.New() if err != nil { return err } go oomLog.StreamOoms(outStream) go func() { for oomInstance := range outStream { // Surface OOM and OOM kill events. newEvent := &info.Event{ ContainerName: oomInstance.ContainerName, Timestamp: oomInstance.TimeOfDeath, EventType: info.EventOom, } err := self.eventHandler.AddEvent(newEvent) if err != nil { glog.Errorf("failed to add OOM event for %q: %v", oomInstance.ContainerName, err) } glog.V(3).Infof("Created an OOM event in container %q at %v", oomInstance.ContainerName, oomInstance.TimeOfDeath) newEvent = &info.Event{ ContainerName: oomInstance.VictimContainerName, Timestamp: oomInstance.TimeOfDeath, EventType: info.EventOomKill, EventData: info.EventData{ OomKill: &info.OomKillEventData{ Pid: oomInstance.Pid, ProcessName: oomInstance.ProcessName, }, }, } err = self.eventHandler.AddEvent(newEvent) if err != nil { glog.Errorf("failed to add OOM kill event for %q: %v", oomInstance.ContainerName, err) } } }() return nil } // can be called by the api which will take events returned on the channel func (self *manager) WatchForEvents(request *events.Request) (*events.EventChannel, error) { return self.eventHandler.WatchEvents(request) } // can be called by the api which will return all events satisfying the request func (self *manager) GetPastEvents(request *events.Request) ([]*info.Event, error) { return self.eventHandler.GetEvents(request) } // called by the api when a client is no longer listening to the channel func (self *manager) CloseEventChannel(watch_id int) { self.eventHandler.StopWatch(watch_id) } // Parses the events StoragePolicy from the flags. func parseEventsStoragePolicy() events.StoragePolicy { policy := events.DefaultStoragePolicy() // Parse max age. parts := strings.Split(*eventStorageAgeLimit, ",") for _, part := range parts { items := strings.Split(part, "=") if len(items) != 2 { glog.Warningf("Unknown event storage policy %q when parsing max age", part) continue } dur, err := time.ParseDuration(items[1]) if err != nil { glog.Warningf("Unable to parse event max age duration %q: %v", items[1], err) continue } if items[0] == "default" { policy.DefaultMaxAge = dur continue } policy.PerTypeMaxAge[info.EventType(items[0])] = dur } // Parse max number. parts = strings.Split(*eventStorageEventLimit, ",") for _, part := range parts { items := strings.Split(part, "=") if len(items) != 2 { glog.Warningf("Unknown event storage policy %q when parsing max event limit", part) continue } val, err := strconv.Atoi(items[1]) if err != nil { glog.Warningf("Unable to parse integer from %q: %v", items[1], err) continue } if items[0] == "default" { policy.DefaultMaxNumEvents = val continue } policy.PerTypeMaxNumEvents[info.EventType(items[0])] = val } return policy } func (m *manager) DockerImages() ([]info.DockerImage, error) { return docker.Images() } func (m *manager) DockerInfo() (info.DockerStatus, error) { return docker.Status() } func (m *manager) DebugInfo() map[string][]string { debugInfo := container.DebugInfo() // Get unique containers. var conts map[*containerData]struct{} func() { m.containersLock.RLock() defer m.containersLock.RUnlock() conts = make(map[*containerData]struct{}, len(m.containers)) for _, c := range m.containers { conts[c] = struct{}{} } }() // List containers. lines := make([]string, 0, len(conts)) for cont := range conts { lines = append(lines, cont.info.Name) if cont.info.Namespace != "" { lines = append(lines, fmt.Sprintf("\tNamespace: %s", cont.info.Namespace)) } if len(cont.info.Aliases) != 0 { lines = append(lines, "\tAliases:") for _, alias := range cont.info.Aliases { lines = append(lines, fmt.Sprintf("\t\t%s", alias)) } } } debugInfo["Managed containers"] = lines return debugInfo } func (self *manager) getFsInfoByDeviceName(deviceName string) (v2.FsInfo, error) { mountPoint, err := self.fsInfo.GetMountpointForDevice(deviceName) if err != nil { return v2.FsInfo{}, fmt.Errorf("failed to get mount point for device %q: %v", deviceName, err) } infos, err := self.GetFsInfo("") if err != nil { return v2.FsInfo{}, err } for _, info := range infos { if info.Mountpoint == mountPoint { return info, nil } } return v2.FsInfo{}, fmt.Errorf("cannot find filesystem info for device %q", deviceName) } func getVersionInfo() (*info.VersionInfo, error) { kernel_version := machine.KernelVersion() container_os := machine.ContainerOsVersion() docker_version := docker.VersionString() docker_api_version := docker.APIVersionString() return &info.VersionInfo{ KernelVersion: kernel_version, ContainerOsVersion: container_os, DockerVersion: docker_version, DockerAPIVersion: docker_api_version, CadvisorVersion: version.Info["version"], CadvisorRevision: version.Info["revision"], }, nil } // Helper for accumulating partial failures. type partialFailure []string func (f *partialFailure) append(id, operation string, err error) { *f = append(*f, fmt.Sprintf("[%q: %s: %s]", id, operation, err)) } func (f partialFailure) Error() string { return fmt.Sprintf("partial failures: %s", strings.Join(f, ", ")) } func (f partialFailure) OrNil() error { if len(f) == 0 { return nil } return f } cadvisor-0.27.1/manager/manager_test.go000066400000000000000000000242271315410276000200330ustar00rootroot00000000000000// 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" "reflect" "strings" "testing" "time" "net/http" "github.com/google/cadvisor/cache/memory" "github.com/google/cadvisor/collector" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/docker" containertest "github.com/google/cadvisor/container/testing" info "github.com/google/cadvisor/info/v1" itest "github.com/google/cadvisor/info/v1/test" "github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/utils/sysfs/fakesysfs" "github.com/stretchr/testify/assert" ) // TODO(vmarmol): Refactor these tests. func createManagerAndAddContainers( memoryCache *memory.InMemoryCache, sysfs *fakesysfs.FakeSysFs, containers []string, f func(*containertest.MockContainerHandler), t *testing.T, ) *manager { container.ClearContainerHandlerFactories() mif := &manager{ containers: make(map[namespacedContainerName]*containerData), quitChannels: make([]chan error, 0, 2), memoryCache: memoryCache, } for _, name := range containers { mockHandler := containertest.NewMockContainerHandler(name) spec := itest.GenerateRandomContainerSpec(4) mockHandler.On("GetSpec").Return( spec, nil, ).Once() cont, err := newContainerData(name, memoryCache, mockHandler, false, &collector.GenericCollectorManager{}, 60*time.Second, true) if err != nil { t.Fatal(err) } mif.containers[namespacedContainerName{ Name: name, }] = cont // Add Docker containers under their namespace. if strings.HasPrefix(name, "/docker") { mif.containers[namespacedContainerName{ Namespace: docker.DockerNamespace, Name: strings.TrimPrefix(name, "/docker/"), }] = cont } f(mockHandler) } return mif } // Expect a manager with the specified containers and query. Returns the manager, map of ContainerInfo objects, // and map of MockContainerHandler objects.} func expectManagerWithContainers(containers []string, query *info.ContainerInfoRequest, t *testing.T) (*manager, map[string]*info.ContainerInfo, map[string]*containertest.MockContainerHandler) { infosMap := make(map[string]*info.ContainerInfo, len(containers)) handlerMap := make(map[string]*containertest.MockContainerHandler, len(containers)) for _, container := range containers { infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second) } memoryCache := memory.New(time.Duration(query.NumStats)*time.Second, nil) sysfs := &fakesysfs.FakeSysFs{} m := createManagerAndAddContainers( memoryCache, sysfs, containers, func(h *containertest.MockContainerHandler) { cinfo := infosMap[h.Name] ref, err := h.ContainerReference() if err != nil { t.Error(err) } for _, stat := range cinfo.Stats { err = memoryCache.AddStats(ref, stat) if err != nil { t.Error(err) } } spec := cinfo.Spec h.On("ListContainers", container.ListSelf).Return( []info.ContainerReference(nil), nil, ) h.On("GetSpec").Return( spec, nil, ).Once() handlerMap[h.Name] = h }, t, ) return m, infosMap, handlerMap } // Expect a manager with the specified containers and query. Returns the manager, map of ContainerInfo objects, // and map of MockContainerHandler objects.} func expectManagerWithContainersV2(containers []string, query *info.ContainerInfoRequest, t *testing.T) (*manager, map[string]*info.ContainerInfo, map[string]*containertest.MockContainerHandler) { infosMap := make(map[string]*info.ContainerInfo, len(containers)) handlerMap := make(map[string]*containertest.MockContainerHandler, len(containers)) for _, container := range containers { infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second) } memoryCache := memory.New(time.Duration(query.NumStats)*time.Second, nil) sysfs := &fakesysfs.FakeSysFs{} m := createManagerAndAddContainers( memoryCache, sysfs, containers, func(h *containertest.MockContainerHandler) { cinfo := infosMap[h.Name] ref, err := h.ContainerReference() if err != nil { t.Error(err) } for _, stat := range cinfo.Stats { err = memoryCache.AddStats(ref, stat) if err != nil { t.Error(err) } } spec := cinfo.Spec h.On("GetSpec").Return( spec, nil, ).Once() handlerMap[h.Name] = h }, t, ) return m, infosMap, handlerMap } func TestGetContainerInfo(t *testing.T) { containers := []string{ "/c1", "/c2", } query := &info.ContainerInfoRequest{ NumStats: 256, } m, infosMap, handlerMap := expectManagerWithContainers(containers, query, t) returnedInfos := make(map[string]*info.ContainerInfo, len(containers)) for _, container := range containers { cinfo, err := m.GetContainerInfo(container, query) if err != nil { t.Fatalf("Unable to get info for container %v: %v", container, err) } returnedInfos[container] = cinfo } for container, handler := range handlerMap { handler.AssertExpectations(t) returned := returnedInfos[container] expected := infosMap[container] if !reflect.DeepEqual(returned, expected) { t.Errorf("returned unexpected info for container %v; returned %+v; expected %+v", container, returned, expected) } } } func TestGetContainerInfoV2(t *testing.T) { containers := []string{ "/", "/c1", "/c2", } options := v2.RequestOptions{ IdType: v2.TypeName, Count: 1, Recursive: true, } query := &info.ContainerInfoRequest{ NumStats: 2, } m, _, handlerMap := expectManagerWithContainersV2(containers, query, t) infos, err := m.GetContainerInfoV2("/", options) if err != nil { t.Fatalf("GetContainerInfoV2 failed: %v", err) } for container, handler := range handlerMap { handler.AssertExpectations(t) info, ok := infos[container] assert.True(t, ok, "Missing info for container %q", container) assert.NotEqual(t, v2.ContainerSpec{}, info.Spec, "Empty spec for container %q", container) assert.NotEmpty(t, info.Stats, "Missing stats for container %q", container) } } func TestGetContainerInfoV2Failure(t *testing.T) { successful := "/" statless := "/c1" failing := "/c2" containers := []string{ successful, statless, failing, } options := v2.RequestOptions{ IdType: v2.TypeName, Count: 1, Recursive: true, } query := &info.ContainerInfoRequest{ NumStats: 2, } m, _, handlerMap := expectManagerWithContainers(containers, query, t) // Remove /c1 stats err := m.memoryCache.RemoveContainer(statless) if err != nil { t.Fatalf("RemoveContainer failed: %v", err) } // Make GetSpec fail on /c2 mockErr := fmt.Errorf("intentional GetSpec failure") handlerMap[failing].GetSpec() // Use up default GetSpec call, and replace below handlerMap[failing].On("GetSpec").Return(info.ContainerSpec{}, mockErr) handlerMap[failing].On("Exists").Return(true) m.containers[namespacedContainerName{Name: failing}].lastUpdatedTime = time.Time{} // Force GetSpec. infos, err := m.GetContainerInfoV2("/", options) if err == nil { t.Error("Expected error calling GetContainerInfoV2") } // Successful containers still successful. info, ok := infos[successful] assert.True(t, ok, "Missing info for container %q", successful) assert.NotEqual(t, v2.ContainerSpec{}, info.Spec, "Empty spec for container %q", successful) assert.NotEmpty(t, info.Stats, "Missing stats for container %q", successful) // "/c1" present with spec. info, ok = infos[statless] assert.True(t, ok, "Missing info for container %q", statless) assert.NotEqual(t, v2.ContainerSpec{}, info.Spec, "Empty spec for container %q", statless) assert.Empty(t, info.Stats, "Missing stats for container %q", successful) // "/c2" should be present but empty. info, ok = infos[failing] assert.True(t, ok, "Missing info for failed container") assert.Equal(t, v2.ContainerInfo{}, info, "Empty spec for failed container") assert.Empty(t, info.Stats, "Missing stats for failed container") } func TestSubcontainersInfo(t *testing.T) { containers := []string{ "/c1", "/c2", } query := &info.ContainerInfoRequest{ NumStats: 64, } m, _, _ := expectManagerWithContainers(containers, query, t) result, err := m.SubcontainersInfo("/", query) if err != nil { t.Fatalf("expected to succeed: %s", err) } if len(result) != len(containers) { t.Errorf("expected to received containers: %v, but received: %v", containers, result) } for _, res := range result { found := false for _, name := range containers { if res.Name == name { found = true break } } if !found { t.Errorf("unexpected container %q in result, expected one of %v", res.Name, containers) } } } func TestDockerContainersInfo(t *testing.T) { containers := []string{ "/docker/c1a", "/docker/c2a", } query := &info.ContainerInfoRequest{ NumStats: 2, } m, _, _ := expectManagerWithContainers(containers, query, t) result, err := m.DockerContainer("c1a", query) if err != nil { t.Fatalf("expected to succeed: %s", err) } if result.Name != containers[0] { t.Errorf("Unexpected container %q in result. Expected container %q", result.Name, containers[0]) } result, err = m.DockerContainer("c2", query) if err != nil { t.Fatalf("expected to succeed: %s", err) } if result.Name != containers[1] { t.Errorf("Unexpected container %q in result. Expected container %q", result.Name, containers[1]) } result, err = m.DockerContainer("c", query) expectedError := "unable to find container. Container \"c\" is not unique" if err == nil { t.Errorf("expected error %q but received %q", expectedError, err) } } func TestNewNilManager(t *testing.T) { _, err := New(nil, nil, 60*time.Second, true, container.MetricSet{}, http.DefaultClient) if err == nil { t.Fatalf("Expected nil manager to return error") } } cadvisor-0.27.1/manager/watcher/000077500000000000000000000000001315410276000164615ustar00rootroot00000000000000cadvisor-0.27.1/manager/watcher/raw/000077500000000000000000000000001315410276000172525ustar00rootroot00000000000000cadvisor-0.27.1/manager/watcher/raw/raw.go000066400000000000000000000141421315410276000203740ustar00rootroot00000000000000// 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 defines types for sub-container events and also // defines an interface for container operation handlers. package raw import ( "fmt" "io/ioutil" "os" "path" "strings" "github.com/google/cadvisor/container/common" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/manager/watcher" "github.com/golang/glog" "golang.org/x/exp/inotify" ) type rawContainerWatcher struct { // Absolute path to the root of the cgroup hierarchies cgroupPaths map[string]string cgroupSubsystems *libcontainer.CgroupSubsystems // Inotify event watcher. watcher *common.InotifyWatcher // Signal for watcher thread to stop. stopWatcher chan error } func NewRawContainerWatcher() (watcher.ContainerWatcher, error) { cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() if err != nil { return nil, fmt.Errorf("failed to get cgroup subsystems: %v", err) } if len(cgroupSubsystems.Mounts) == 0 { return nil, fmt.Errorf("failed to find supported cgroup mounts for the raw factory") } watcher, err := common.NewInotifyWatcher() if err != nil { return nil, err } rawWatcher := &rawContainerWatcher{ cgroupPaths: common.MakeCgroupPaths(cgroupSubsystems.MountPoints, "/"), cgroupSubsystems: &cgroupSubsystems, watcher: watcher, stopWatcher: make(chan error), } return rawWatcher, nil } func (self *rawContainerWatcher) Start(events chan watcher.ContainerEvent) error { // Watch this container (all its cgroups) and all subdirectories. for _, cgroupPath := range self.cgroupPaths { _, err := self.watchDirectory(cgroupPath, "/") if err != nil { return err } } // Process the events received from the kernel. go func() { for { select { case event := <-self.watcher.Event(): err := self.processEvent(event, events) if err != nil { glog.Warningf("Error while processing event (%+v): %v", event, err) } case err := <-self.watcher.Error(): glog.Warningf("Error while watching %q:", "/", err) case <-self.stopWatcher: err := self.watcher.Close() if err == nil { self.stopWatcher <- err return } } } }() return nil } func (self *rawContainerWatcher) Stop() error { // Rendezvous with the watcher thread. self.stopWatcher <- nil return <-self.stopWatcher } // Watches the specified directory and all subdirectories. Returns whether the path was // already being watched and an error (if any). func (self *rawContainerWatcher) watchDirectory(dir string, containerName string) (bool, error) { alreadyWatching, err := self.watcher.AddWatch(containerName, dir) if err != nil { return alreadyWatching, err } // Remove the watch if further operations failed. cleanup := true defer func() { if cleanup { _, err := self.watcher.RemoveWatch(containerName, dir) if err != nil { glog.Warningf("Failed to remove inotify watch for %q: %v", dir, err) } } }() // TODO(vmarmol): We should re-do this once we're done to ensure directories were not added in the meantime. // Watch subdirectories as well. entries, err := ioutil.ReadDir(dir) if err != nil { return alreadyWatching, err } for _, entry := range entries { if entry.IsDir() { entryPath := path.Join(dir, entry.Name()) _, err = self.watchDirectory(entryPath, path.Join(containerName, entry.Name())) if err != nil { glog.Errorf("Failed to watch directory %q: %v", entryPath, err) if os.IsNotExist(err) { // The directory may have been removed before watching. Try to watch the other // subdirectories. (https://github.com/kubernetes/kubernetes/issues/28997) continue } return alreadyWatching, err } } } cleanup = false return alreadyWatching, nil } func (self *rawContainerWatcher) processEvent(event *inotify.Event, events chan watcher.ContainerEvent) error { // Convert the inotify event type to a container create or delete. var eventType watcher.ContainerEventType switch { case (event.Mask & inotify.IN_CREATE) > 0: eventType = watcher.ContainerAdd case (event.Mask & inotify.IN_DELETE) > 0: eventType = watcher.ContainerDelete case (event.Mask & inotify.IN_MOVED_FROM) > 0: eventType = watcher.ContainerDelete case (event.Mask & inotify.IN_MOVED_TO) > 0: eventType = watcher.ContainerAdd default: // Ignore other events. return nil } // Derive the container name from the path name. var containerName string for _, mount := range self.cgroupSubsystems.Mounts { mountLocation := path.Clean(mount.Mountpoint) + "/" if strings.HasPrefix(event.Name, mountLocation) { containerName = event.Name[len(mountLocation)-1:] break } } if containerName == "" { return fmt.Errorf("unable to detect container from watch event on directory %q", event.Name) } // Maintain the watch for the new or deleted container. switch eventType { case watcher.ContainerAdd: // New container was created, watch it. alreadyWatched, err := self.watchDirectory(event.Name, containerName) if err != nil { return err } // Only report container creation once. if alreadyWatched { return nil } case watcher.ContainerDelete: // Container was deleted, stop watching for it. lastWatched, err := self.watcher.RemoveWatch(containerName, event.Name) if err != nil { return err } // Only report container deletion once. if !lastWatched { return nil } default: return fmt.Errorf("unknown event type %v", eventType) } // Deliver the event. events <- watcher.ContainerEvent{ EventType: eventType, Name: containerName, WatchSource: watcher.Raw, } return nil } cadvisor-0.27.1/manager/watcher/rkt/000077500000000000000000000000001315410276000172615ustar00rootroot00000000000000cadvisor-0.27.1/manager/watcher/rkt/rkt.go000066400000000000000000000077301315410276000204170ustar00rootroot00000000000000// Copyright 2016 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 rkt implements the watcher interface for rkt package rkt import ( "path/filepath" "time" "github.com/google/cadvisor/container/rkt" "github.com/google/cadvisor/manager/watcher" rktapi "github.com/coreos/rkt/api/v1alpha" "github.com/golang/glog" "golang.org/x/net/context" ) type rktContainerWatcher struct { // Signal for watcher thread to stop. stopWatcher chan error } func NewRktContainerWatcher() (watcher.ContainerWatcher, error) { watcher := &rktContainerWatcher{ stopWatcher: make(chan error), } return watcher, nil } func (self *rktContainerWatcher) Start(events chan watcher.ContainerEvent) error { go self.detectRktContainers(events) return nil } func (self *rktContainerWatcher) Stop() error { // Rendezvous with the watcher thread. self.stopWatcher <- nil return nil } func (self *rktContainerWatcher) detectRktContainers(events chan watcher.ContainerEvent) { glog.Infof("starting detectRktContainers thread") ticker := time.Tick(10 * time.Second) curpods := make(map[string]*rktapi.Pod) for { select { case <-ticker: pods, err := listRunningPods() if err != nil { glog.Errorf("detectRktContainers: listRunningPods failed: %v", err) continue } curpods = self.syncRunningPods(pods, events, curpods) case <-self.stopWatcher: glog.Infof("Exiting rktContainer Thread") return } } } func (self *rktContainerWatcher) syncRunningPods(pods []*rktapi.Pod, events chan watcher.ContainerEvent, curpods map[string]*rktapi.Pod) map[string]*rktapi.Pod { newpods := make(map[string]*rktapi.Pod) for _, pod := range pods { newpods[pod.Id] = pod // if pods become mutable, have to handle this better if _, ok := curpods[pod.Id]; !ok { // should create all cgroups not including system.slice // i.e. /system.slice/rkt-test.service and /system.slice/rkt-test.service/system.slice/pause.service for _, cgroup := range podToCgroup(pod) { self.sendUpdateEvent(cgroup, events) } } } for id, pod := range curpods { if _, ok := newpods[id]; !ok { for _, cgroup := range podToCgroup(pod) { glog.Infof("cgroup to delete = %v", cgroup) self.sendDestroyEvent(cgroup, events) } } } return newpods } func (self *rktContainerWatcher) sendUpdateEvent(cgroup string, events chan watcher.ContainerEvent) { events <- watcher.ContainerEvent{ EventType: watcher.ContainerAdd, Name: cgroup, WatchSource: watcher.Rkt, } } func (self *rktContainerWatcher) sendDestroyEvent(cgroup string, events chan watcher.ContainerEvent) { events <- watcher.ContainerEvent{ EventType: watcher.ContainerDelete, Name: cgroup, WatchSource: watcher.Rkt, } } func listRunningPods() ([]*rktapi.Pod, error) { client, err := rkt.Client() if err != nil { return nil, err } resp, err := client.ListPods(context.Background(), &rktapi.ListPodsRequest{ // Specify the request: Fetch and print only running pods and their details. Detail: true, Filters: []*rktapi.PodFilter{ { States: []rktapi.PodState{rktapi.PodState_POD_STATE_RUNNING}, }, }, }) if err != nil { return nil, err } return resp.Pods, nil } func podToCgroup(pod *rktapi.Pod) []string { cgroups := make([]string, 1+len(pod.Apps), 1+len(pod.Apps)) baseCgroup := pod.Cgroup cgroups[0] = baseCgroup for i, app := range pod.Apps { cgroups[i+1] = filepath.Join(baseCgroup, "system.slice", app.Name+".service") } return cgroups } cadvisor-0.27.1/manager/watcher/watcher.go000066400000000000000000000027541315410276000204550ustar00rootroot00000000000000// Copyright 2016 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 defines types for sub-container events and also // defines an interface for container operation handlers. package watcher // SubcontainerEventType indicates an addition or deletion event. type ContainerEventType int const ( ContainerAdd ContainerEventType = iota ContainerDelete ) type ContainerWatchSource int const ( Raw ContainerWatchSource = iota Rkt ) // ContainerEvent represents a type ContainerEvent struct { // The type of event that occurred. EventType ContainerEventType // The full container name of the container where the event occurred. Name string // The watcher that detected this change event WatchSource ContainerWatchSource } type ContainerWatcher interface { // Registers a channel to listen for events affecting subcontainers (recursively). Start(events chan ContainerEvent) error // Stops watching for subcontainer changes. Stop() error } cadvisor-0.27.1/metrics/000077500000000000000000000000001315410276000150605ustar00rootroot00000000000000cadvisor-0.27.1/metrics/prometheus.go000066400000000000000000000734461315410276000176200ustar00rootroot00000000000000// 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 metrics import ( "fmt" "regexp" "time" info "github.com/google/cadvisor/info/v1" "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" ) // infoProvider will usually be manager.Manager, but can be swapped out for testing. type infoProvider interface { // SubcontainersInfo provides information about all subcontainers of the // specified container including itself. SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) // GetVersionInfo provides information about the version. GetVersionInfo() (*info.VersionInfo, error) // GetMachineInfo provides information about the machine. GetMachineInfo() (*info.MachineInfo, error) } // metricValue describes a single metric value for a given set of label values // within a parent containerMetric. type metricValue struct { value float64 labels []string } type metricValues []metricValue // asFloat64 converts a uint64 into a float64. func asFloat64(v uint64) float64 { return float64(v) } // asNanosecondsToSeconds converts nanoseconds into a float64 representing seconds. func asNanosecondsToSeconds(v uint64) float64 { return float64(v) / float64(time.Second) } // fsValues is a helper method for assembling per-filesystem stats. func fsValues(fsStats []info.FsStats, valueFn func(*info.FsStats) float64) metricValues { values := make(metricValues, 0, len(fsStats)) for _, stat := range fsStats { values = append(values, metricValue{ value: valueFn(&stat), labels: []string{stat.Device}, }) } return values } // ioValues is a helper method for assembling per-disk and per-filesystem stats. func ioValues(ioStats []info.PerDiskStats, ioType string, ioValueFn func(uint64) float64, fsStats []info.FsStats, valueFn func(*info.FsStats) float64) metricValues { values := make(metricValues, 0, len(ioStats)+len(fsStats)) for _, stat := range ioStats { values = append(values, metricValue{ value: ioValueFn(stat.Stats[ioType]), labels: []string{stat.Device}, }) } for _, stat := range fsStats { values = append(values, metricValue{ value: valueFn(&stat), labels: []string{stat.Device}, }) } return values } // containerMetric describes a multi-dimensional metric used for exposing a // certain type of container statistic. type containerMetric struct { name string help string valueType prometheus.ValueType extraLabels []string condition func(s info.ContainerSpec) bool getValues func(s *info.ContainerStats) metricValues } func (cm *containerMetric) desc(baseLabels []string) *prometheus.Desc { return prometheus.NewDesc(cm.name, cm.help, append(baseLabels, cm.extraLabels...), nil) } // ContainerLabelsFunc defines all base labels and their values attached to // each metric exported by cAdvisor. type ContainerLabelsFunc func(*info.ContainerInfo) map[string]string // PrometheusCollector implements prometheus.Collector. type PrometheusCollector struct { infoProvider infoProvider errors prometheus.Gauge containerMetrics []containerMetric containerLabelsFunc ContainerLabelsFunc } // NewPrometheusCollector returns a new PrometheusCollector. The passed // ContainerLabelsFunc specifies which base labels will be attached to all // exported metrics. If left to nil, the DefaultContainerLabels function // will be used instead. func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc) *PrometheusCollector { if f == nil { f = DefaultContainerLabels } c := &PrometheusCollector{ infoProvider: i, containerLabelsFunc: f, errors: prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: "container", Name: "scrape_error", Help: "1 if there was an error while getting container metrics, 0 otherwise", }), containerMetrics: []containerMetric{ { name: "container_last_seen", help: "Last time a container was seen by the exporter", valueType: prometheus.GaugeValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(time.Now().Unix())}} }, }, { name: "container_cpu_user_seconds_total", help: "Cumulative user cpu time consumed in seconds.", valueType: prometheus.CounterValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Cpu.Usage.User) / float64(time.Second)}} }, }, { name: "container_cpu_system_seconds_total", help: "Cumulative system cpu time consumed in seconds.", valueType: prometheus.CounterValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Cpu.Usage.System) / float64(time.Second)}} }, }, { name: "container_cpu_usage_seconds_total", help: "Cumulative cpu time consumed per cpu in seconds.", valueType: prometheus.CounterValue, extraLabels: []string{"cpu"}, getValues: func(s *info.ContainerStats) metricValues { values := make(metricValues, 0, len(s.Cpu.Usage.PerCpu)) for i, value := range s.Cpu.Usage.PerCpu { if value > 0 { values = append(values, metricValue{ value: float64(value) / float64(time.Second), labels: []string{fmt.Sprintf("cpu%02d", i)}, }) } } return values }, }, { name: "container_cpu_cfs_periods_total", help: "Number of elapsed enforcement period intervals.", valueType: prometheus.CounterValue, condition: func(s info.ContainerSpec) bool { return s.Cpu.Quota != 0 }, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Cpu.CFS.Periods)}} }, }, { name: "container_cpu_cfs_throttled_periods_total", help: "Number of throttled period intervals.", valueType: prometheus.CounterValue, condition: func(s info.ContainerSpec) bool { return s.Cpu.Quota != 0 }, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Cpu.CFS.ThrottledPeriods)}} }, }, { name: "container_cpu_cfs_throttled_seconds_total", help: "Total time duration the container has been throttled.", valueType: prometheus.CounterValue, condition: func(s info.ContainerSpec) bool { return s.Cpu.Quota != 0 }, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Cpu.CFS.ThrottledTime) / float64(time.Second)}} }, }, { name: "container_cpu_load_average_10s", help: "Value of container cpu load average over the last 10 seconds.", valueType: prometheus.GaugeValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Cpu.LoadAverage)}} }, }, { name: "container_memory_cache", help: "Number of bytes of page cache memory.", valueType: prometheus.GaugeValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Memory.Cache)}} }, }, { name: "container_memory_rss", help: "Size of RSS in bytes.", valueType: prometheus.GaugeValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Memory.RSS)}} }, }, { name: "container_memory_swap", help: "Container swap usage in bytes.", valueType: prometheus.GaugeValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Memory.Swap)}} }, }, { name: "container_memory_failcnt", help: "Number of memory usage hits limits", valueType: prometheus.CounterValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Memory.Failcnt)}} }, }, { name: "container_memory_usage_bytes", help: "Current memory usage in bytes.", valueType: prometheus.GaugeValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Memory.Usage)}} }, }, { name: "container_memory_working_set_bytes", help: "Current working set in bytes.", valueType: prometheus.GaugeValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{{value: float64(s.Memory.WorkingSet)}} }, }, { name: "container_memory_failures_total", help: "Cumulative count of memory allocation failures.", valueType: prometheus.CounterValue, extraLabels: []string{"type", "scope"}, getValues: func(s *info.ContainerStats) metricValues { return metricValues{ { value: float64(s.Memory.ContainerData.Pgfault), labels: []string{"pgfault", "container"}, }, { value: float64(s.Memory.ContainerData.Pgmajfault), labels: []string{"pgmajfault", "container"}, }, { value: float64(s.Memory.HierarchicalData.Pgfault), labels: []string{"pgfault", "hierarchy"}, }, { value: float64(s.Memory.HierarchicalData.Pgmajfault), labels: []string{"pgmajfault", "hierarchy"}, }, } }, }, { name: "container_fs_inodes_free", help: "Number of available Inodes", valueType: prometheus.GaugeValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return fsValues(s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.InodesFree) }) }, }, { name: "container_fs_inodes_total", help: "Number of Inodes", valueType: prometheus.GaugeValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return fsValues(s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.Inodes) }) }, }, { name: "container_fs_limit_bytes", help: "Number of bytes that can be consumed by the container on this filesystem.", valueType: prometheus.GaugeValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return fsValues(s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.Limit) }) }, }, { name: "container_fs_usage_bytes", help: "Number of bytes that are consumed by the container on this filesystem.", valueType: prometheus.GaugeValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return fsValues(s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.Usage) }) }, }, { name: "container_fs_reads_bytes_total", help: "Cumulative count of bytes read", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoServiceBytes, "Read", asFloat64, nil, nil, ) }, }, { name: "container_fs_reads_total", help: "Cumulative count of reads completed", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoServiced, "Read", asFloat64, s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.ReadsCompleted) }, ) }, }, { name: "container_fs_sector_reads_total", help: "Cumulative count of sector reads completed", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.Sectors, "Read", asFloat64, s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.SectorsRead) }, ) }, }, { name: "container_fs_reads_merged_total", help: "Cumulative count of reads merged", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoMerged, "Read", asFloat64, s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.ReadsMerged) }, ) }, }, { name: "container_fs_read_seconds_total", help: "Cumulative count of seconds spent reading", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoServiceTime, "Read", asNanosecondsToSeconds, s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.ReadTime) / float64(time.Second) }, ) }, }, { name: "container_fs_writes_bytes_total", help: "Cumulative count of bytes written", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoServiceBytes, "Write", asFloat64, nil, nil, ) }, }, { name: "container_fs_writes_total", help: "Cumulative count of writes completed", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoServiced, "Write", asFloat64, s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.WritesCompleted) }, ) }, }, { name: "container_fs_sector_writes_total", help: "Cumulative count of sector writes completed", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.Sectors, "Write", asFloat64, s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.SectorsWritten) }, ) }, }, { name: "container_fs_writes_merged_total", help: "Cumulative count of writes merged", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoMerged, "Write", asFloat64, s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.WritesMerged) }, ) }, }, { name: "container_fs_write_seconds_total", help: "Cumulative count of seconds spent writing", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoServiceTime, "Write", asNanosecondsToSeconds, s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.WriteTime) / float64(time.Second) }, ) }, }, { name: "container_fs_io_current", help: "Number of I/Os currently in progress", valueType: prometheus.GaugeValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoQueued, "Total", asFloat64, s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.IoInProgress) }, ) }, }, { name: "container_fs_io_time_seconds_total", help: "Cumulative count of seconds spent doing I/Os", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return ioValues( s.DiskIo.IoServiceTime, "Total", asNanosecondsToSeconds, s.Filesystem, func(fs *info.FsStats) float64 { return float64(float64(fs.IoTime) / float64(time.Second)) }, ) }, }, { name: "container_fs_io_time_weighted_seconds_total", help: "Cumulative weighted I/O time in seconds", valueType: prometheus.CounterValue, extraLabels: []string{"device"}, getValues: func(s *info.ContainerStats) metricValues { return fsValues(s.Filesystem, func(fs *info.FsStats) float64 { return float64(fs.WeightedIoTime) / float64(time.Second) }) }, }, { name: "container_network_receive_bytes_total", help: "Cumulative count of bytes received", valueType: prometheus.CounterValue, extraLabels: []string{"interface"}, getValues: func(s *info.ContainerStats) metricValues { values := make(metricValues, 0, len(s.Network.Interfaces)) for _, value := range s.Network.Interfaces { values = append(values, metricValue{ value: float64(value.RxBytes), labels: []string{value.Name}, }) } return values }, }, { name: "container_network_receive_packets_total", help: "Cumulative count of packets received", valueType: prometheus.CounterValue, extraLabels: []string{"interface"}, getValues: func(s *info.ContainerStats) metricValues { values := make(metricValues, 0, len(s.Network.Interfaces)) for _, value := range s.Network.Interfaces { values = append(values, metricValue{ value: float64(value.RxPackets), labels: []string{value.Name}, }) } return values }, }, { name: "container_network_receive_packets_dropped_total", help: "Cumulative count of packets dropped while receiving", valueType: prometheus.CounterValue, extraLabels: []string{"interface"}, getValues: func(s *info.ContainerStats) metricValues { values := make(metricValues, 0, len(s.Network.Interfaces)) for _, value := range s.Network.Interfaces { values = append(values, metricValue{ value: float64(value.RxDropped), labels: []string{value.Name}, }) } return values }, }, { name: "container_network_receive_errors_total", help: "Cumulative count of errors encountered while receiving", valueType: prometheus.CounterValue, extraLabels: []string{"interface"}, getValues: func(s *info.ContainerStats) metricValues { values := make(metricValues, 0, len(s.Network.Interfaces)) for _, value := range s.Network.Interfaces { values = append(values, metricValue{ value: float64(value.RxErrors), labels: []string{value.Name}, }) } return values }, }, { name: "container_network_transmit_bytes_total", help: "Cumulative count of bytes transmitted", valueType: prometheus.CounterValue, extraLabels: []string{"interface"}, getValues: func(s *info.ContainerStats) metricValues { values := make(metricValues, 0, len(s.Network.Interfaces)) for _, value := range s.Network.Interfaces { values = append(values, metricValue{ value: float64(value.TxBytes), labels: []string{value.Name}, }) } return values }, }, { name: "container_network_transmit_packets_total", help: "Cumulative count of packets transmitted", valueType: prometheus.CounterValue, extraLabels: []string{"interface"}, getValues: func(s *info.ContainerStats) metricValues { values := make(metricValues, 0, len(s.Network.Interfaces)) for _, value := range s.Network.Interfaces { values = append(values, metricValue{ value: float64(value.TxPackets), labels: []string{value.Name}, }) } return values }, }, { name: "container_network_transmit_packets_dropped_total", help: "Cumulative count of packets dropped while transmitting", valueType: prometheus.CounterValue, extraLabels: []string{"interface"}, getValues: func(s *info.ContainerStats) metricValues { values := make(metricValues, 0, len(s.Network.Interfaces)) for _, value := range s.Network.Interfaces { values = append(values, metricValue{ value: float64(value.TxDropped), labels: []string{value.Name}, }) } return values }, }, { name: "container_network_transmit_errors_total", help: "Cumulative count of errors encountered while transmitting", valueType: prometheus.CounterValue, extraLabels: []string{"interface"}, getValues: func(s *info.ContainerStats) metricValues { values := make(metricValues, 0, len(s.Network.Interfaces)) for _, value := range s.Network.Interfaces { values = append(values, metricValue{ value: float64(value.TxErrors), labels: []string{value.Name}, }) } return values }, }, { name: "container_network_tcp_usage_total", help: "tcp connection usage statistic for container", valueType: prometheus.GaugeValue, extraLabels: []string{"tcp_state"}, getValues: func(s *info.ContainerStats) metricValues { return metricValues{ { value: float64(s.Network.Tcp.Established), labels: []string{"established"}, }, { value: float64(s.Network.Tcp.SynSent), labels: []string{"synsent"}, }, { value: float64(s.Network.Tcp.SynRecv), labels: []string{"synrecv"}, }, { value: float64(s.Network.Tcp.FinWait1), labels: []string{"finwait1"}, }, { value: float64(s.Network.Tcp.FinWait2), labels: []string{"finwait2"}, }, { value: float64(s.Network.Tcp.TimeWait), labels: []string{"timewait"}, }, { value: float64(s.Network.Tcp.Close), labels: []string{"close"}, }, { value: float64(s.Network.Tcp.CloseWait), labels: []string{"closewait"}, }, { value: float64(s.Network.Tcp.LastAck), labels: []string{"lastack"}, }, { value: float64(s.Network.Tcp.Listen), labels: []string{"listen"}, }, { value: float64(s.Network.Tcp.Closing), labels: []string{"closing"}, }, } }, }, { name: "container_network_udp_usage_total", help: "udp connection usage statistic for container", valueType: prometheus.GaugeValue, extraLabels: []string{"udp_state"}, getValues: func(s *info.ContainerStats) metricValues { return metricValues{ { value: float64(s.Network.Udp.Listen), labels: []string{"listen"}, }, { value: float64(s.Network.Udp.Dropped), labels: []string{"dropped"}, }, { value: float64(s.Network.Udp.RxQueued), labels: []string{"rxqueued"}, }, { value: float64(s.Network.Udp.TxQueued), labels: []string{"txqueued"}, }, } }, }, { name: "container_tasks_state", help: "Number of tasks in given state", extraLabels: []string{"state"}, valueType: prometheus.GaugeValue, getValues: func(s *info.ContainerStats) metricValues { return metricValues{ { value: float64(s.TaskStats.NrSleeping), labels: []string{"sleeping"}, }, { value: float64(s.TaskStats.NrRunning), labels: []string{"running"}, }, { value: float64(s.TaskStats.NrStopped), labels: []string{"stopped"}, }, { value: float64(s.TaskStats.NrUninterruptible), labels: []string{"uninterruptible"}, }, { value: float64(s.TaskStats.NrIoWait), labels: []string{"iowaiting"}, }, } }, }, }, } return c } var ( versionInfoDesc = prometheus.NewDesc("cadvisor_version_info", "A metric with a constant '1' value labeled by kernel version, OS version, docker version, cadvisor version & cadvisor revision.", []string{"kernelVersion", "osVersion", "dockerVersion", "cadvisorVersion", "cadvisorRevision"}, nil) machineInfoCoresDesc = prometheus.NewDesc("machine_cpu_cores", "Number of CPU cores on the machine.", nil, nil) machineInfoMemoryDesc = prometheus.NewDesc("machine_memory_bytes", "Amount of memory installed on the machine.", nil, nil) ) // Describe describes all the metrics ever exported by cadvisor. It // implements prometheus.PrometheusCollector. func (c *PrometheusCollector) Describe(ch chan<- *prometheus.Desc) { c.errors.Describe(ch) for _, cm := range c.containerMetrics { ch <- cm.desc([]string{}) } ch <- versionInfoDesc ch <- machineInfoCoresDesc ch <- machineInfoMemoryDesc } // Collect fetches the stats from all containers and delivers them as // Prometheus metrics. It implements prometheus.PrometheusCollector. func (c *PrometheusCollector) Collect(ch chan<- prometheus.Metric) { c.errors.Set(0) c.collectMachineInfo(ch) c.collectVersionInfo(ch) c.collectContainersInfo(ch) c.errors.Collect(ch) } const ( // ContainerLabelPrefix is the prefix added to all container labels. ContainerLabelPrefix = "container_label_" // ContainerEnvPrefix is the prefix added to all env variable labels. ContainerEnvPrefix = "container_env_" // LabelID is the name of the id label. LabelID = "id" // LabelName is the name of the name label. LabelName = "name" // LabelImage is the name of the image label. LabelImage = "image" ) // DefaultContainerLabels implements ContainerLabelsFunc. It exports the // container name, first alias, image name as well as all its env and label // values. func DefaultContainerLabels(container *info.ContainerInfo) map[string]string { set := map[string]string{LabelID: container.Name} if len(container.Aliases) > 0 { set[LabelName] = container.Aliases[0] } if image := container.Spec.Image; len(image) > 0 { set[LabelImage] = image } for k, v := range container.Spec.Labels { set[ContainerLabelPrefix+k] = v } for k, v := range container.Spec.Envs { set[ContainerEnvPrefix+k] = v } return set } func (c *PrometheusCollector) collectContainersInfo(ch chan<- prometheus.Metric) { containers, err := c.infoProvider.SubcontainersInfo("/", &info.ContainerInfoRequest{NumStats: 1}) if err != nil { c.errors.Set(1) glog.Warningf("Couldn't get containers: %s", err) return } for _, container := range containers { labels, values := []string{}, []string{} for l, v := range c.containerLabelsFunc(container) { labels = append(labels, sanitizeLabelName(l)) values = append(values, v) } // Container spec desc := prometheus.NewDesc("container_start_time_seconds", "Start time of the container since unix epoch in seconds.", labels, nil) ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.CreationTime.Unix()), values...) if container.Spec.HasCpu { desc = prometheus.NewDesc("container_spec_cpu_period", "CPU period of the container.", labels, nil) ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.Cpu.Period), values...) if container.Spec.Cpu.Quota != 0 { desc = prometheus.NewDesc("container_spec_cpu_quota", "CPU quota of the container.", labels, nil) ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.Cpu.Quota), values...) } desc := prometheus.NewDesc("container_spec_cpu_shares", "CPU share of the container.", labels, nil) ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.Cpu.Limit), values...) } if container.Spec.HasMemory { desc := prometheus.NewDesc("container_spec_memory_limit_bytes", "Memory limit for the container.", labels, nil) ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, specMemoryValue(container.Spec.Memory.Limit), values...) desc = prometheus.NewDesc("container_spec_memory_swap_limit_bytes", "Memory swap limit for the container.", labels, nil) ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, specMemoryValue(container.Spec.Memory.SwapLimit), values...) } // Now for the actual metrics stats := container.Stats[0] for _, cm := range c.containerMetrics { if cm.condition != nil && !cm.condition(container.Spec) { continue } desc := cm.desc(labels) for _, metricValue := range cm.getValues(stats) { ch <- prometheus.MustNewConstMetric(desc, cm.valueType, float64(metricValue.value), append(values, metricValue.labels...)...) } } } } func (c *PrometheusCollector) collectVersionInfo(ch chan<- prometheus.Metric) { versionInfo, err := c.infoProvider.GetVersionInfo() if err != nil { c.errors.Set(1) glog.Warningf("Couldn't get version info: %s", err) return } ch <- prometheus.MustNewConstMetric(versionInfoDesc, prometheus.GaugeValue, 1, []string{versionInfo.KernelVersion, versionInfo.ContainerOsVersion, versionInfo.DockerVersion, versionInfo.CadvisorVersion, versionInfo.CadvisorRevision}...) } func (c *PrometheusCollector) collectMachineInfo(ch chan<- prometheus.Metric) { machineInfo, err := c.infoProvider.GetMachineInfo() if err != nil { c.errors.Set(1) glog.Warningf("Couldn't get machine info: %s", err) return } ch <- prometheus.MustNewConstMetric(machineInfoCoresDesc, prometheus.GaugeValue, float64(machineInfo.NumCores)) ch <- prometheus.MustNewConstMetric(machineInfoMemoryDesc, prometheus.GaugeValue, float64(machineInfo.MemoryCapacity)) } // Size after which we consider memory to be "unlimited". This is not // MaxInt64 due to rounding by the kernel. const maxMemorySize = uint64(1 << 62) func specMemoryValue(v uint64) float64 { if v > maxMemorySize { return 0 } return float64(v) } var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`) // sanitizeLabelName replaces anything that doesn't match // client_label.LabelNameRE with an underscore. func sanitizeLabelName(name string) string { return invalidLabelCharRE.ReplaceAllString(name, "_") } cadvisor-0.27.1/metrics/prometheus_test.go000066400000000000000000000170521315410276000206460ustar00rootroot00000000000000// 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 metrics import ( "errors" "io/ioutil" "net/http" "net/http/httptest" "regexp" "strings" "testing" "time" info "github.com/google/cadvisor/info/v1" "github.com/prometheus/client_golang/prometheus" ) type testSubcontainersInfoProvider struct{} func (p testSubcontainersInfoProvider) GetVersionInfo() (*info.VersionInfo, error) { return &info.VersionInfo{ KernelVersion: "4.1.6-200.fc22.x86_64", ContainerOsVersion: "Fedora 22 (Twenty Two)", DockerVersion: "1.8.1", CadvisorVersion: "0.16.0", CadvisorRevision: "abcdef", }, nil } func (p testSubcontainersInfoProvider) GetMachineInfo() (*info.MachineInfo, error) { return &info.MachineInfo{ NumCores: 4, MemoryCapacity: 1024, }, nil } func (p testSubcontainersInfoProvider) SubcontainersInfo(string, *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { return []*info.ContainerInfo{ { ContainerReference: info.ContainerReference{ Name: "testcontainer", Aliases: []string{"testcontaineralias"}, }, Spec: info.ContainerSpec{ Image: "test", HasCpu: true, Cpu: info.CpuSpec{ Limit: 1000, Period: 100000, Quota: 10000, }, CreationTime: time.Unix(1257894000, 0), Labels: map[string]string{ "foo.label": "bar", }, Envs: map[string]string{ "foo+env": "prod", }, }, Stats: []*info.ContainerStats{ { Cpu: info.CpuStats{ Usage: info.CpuUsage{ Total: 1, PerCpu: []uint64{2, 3, 4, 5}, User: 6, System: 7, }, CFS: info.CpuCFS{ Periods: 723, ThrottledPeriods: 18, ThrottledTime: 1724314000, }, LoadAverage: 2, }, Memory: info.MemoryStats{ Usage: 8, WorkingSet: 9, ContainerData: info.MemoryStatsMemoryData{ Pgfault: 10, Pgmajfault: 11, }, HierarchicalData: info.MemoryStatsMemoryData{ Pgfault: 12, Pgmajfault: 13, }, Cache: 14, RSS: 15, Swap: 8192, }, Network: info.NetworkStats{ InterfaceStats: info.InterfaceStats{ Name: "eth0", RxBytes: 14, RxPackets: 15, RxErrors: 16, RxDropped: 17, TxBytes: 18, TxPackets: 19, TxErrors: 20, TxDropped: 21, }, Interfaces: []info.InterfaceStats{ { Name: "eth0", RxBytes: 14, RxPackets: 15, RxErrors: 16, RxDropped: 17, TxBytes: 18, TxPackets: 19, TxErrors: 20, TxDropped: 21, }, }, Tcp: info.TcpStat{ Established: 13, SynSent: 0, SynRecv: 0, FinWait1: 0, FinWait2: 0, TimeWait: 0, Close: 0, CloseWait: 0, LastAck: 0, Listen: 3, Closing: 0, }, Udp: info.UdpStat{ Listen: 0, Dropped: 0, RxQueued: 0, TxQueued: 0, }, }, Filesystem: []info.FsStats{ { Device: "sda1", InodesFree: 524288, Inodes: 2097152, Limit: 22, Usage: 23, ReadsCompleted: 24, ReadsMerged: 25, SectorsRead: 26, ReadTime: 27, WritesCompleted: 28, WritesMerged: 39, SectorsWritten: 40, WriteTime: 41, IoInProgress: 42, IoTime: 43, WeightedIoTime: 44, }, { Device: "sda2", InodesFree: 262144, Inodes: 2097152, Limit: 37, Usage: 38, ReadsCompleted: 39, ReadsMerged: 40, SectorsRead: 41, ReadTime: 42, WritesCompleted: 43, WritesMerged: 44, SectorsWritten: 45, WriteTime: 46, IoInProgress: 47, IoTime: 48, WeightedIoTime: 49, }, }, TaskStats: info.LoadStats{ NrSleeping: 50, NrRunning: 51, NrStopped: 52, NrUninterruptible: 53, NrIoWait: 54, }, }, }, }, }, nil } var ( includeRe = regexp.MustCompile(`^(?:(?:# HELP |# TYPE )?container_|cadvisor_version_info\{)`) ignoreRe = regexp.MustCompile(`^container_last_seen\{`) ) func TestPrometheusCollector(t *testing.T) { c := NewPrometheusCollector(testSubcontainersInfoProvider{}, func(container *info.ContainerInfo) map[string]string { s := DefaultContainerLabels(container) s["zone.name"] = "hello" return s }) prometheus.MustRegister(c) defer prometheus.Unregister(c) testPrometheusCollector(t, c, "testdata/prometheus_metrics") } func testPrometheusCollector(t *testing.T, c *PrometheusCollector, metricsFile string) { rw := httptest.NewRecorder() prometheus.Handler().ServeHTTP(rw, &http.Request{}) wantMetrics, err := ioutil.ReadFile(metricsFile) if err != nil { t.Fatalf("unable to read input test file %s", metricsFile) } wantLines := strings.Split(string(wantMetrics), "\n") gotLines := strings.Split(string(rw.Body.String()), "\n") // Until the Prometheus Go client library offers better testability // (https://github.com/prometheus/client_golang/issues/58), we simply compare // verbatim text-format metrics outputs, but ignore certain metric lines // whose value depends on the current time or local circumstances. for i, want := range wantLines { if !includeRe.MatchString(want) || ignoreRe.MatchString(want) { continue } if want != gotLines[i] { t.Fatalf("unexpected metric line\nwant: %s\nhave: %s", want, gotLines[i]) } } } type erroringSubcontainersInfoProvider struct { successfulProvider testSubcontainersInfoProvider shouldFail bool } func (p *erroringSubcontainersInfoProvider) GetVersionInfo() (*info.VersionInfo, error) { if p.shouldFail { return nil, errors.New("Oops 1") } return p.successfulProvider.GetVersionInfo() } func (p *erroringSubcontainersInfoProvider) GetMachineInfo() (*info.MachineInfo, error) { if p.shouldFail { return nil, errors.New("Oops 2") } return p.successfulProvider.GetMachineInfo() } func (p *erroringSubcontainersInfoProvider) SubcontainersInfo( a string, r *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { if p.shouldFail { return []*info.ContainerInfo{}, errors.New("Oops 3") } return p.successfulProvider.SubcontainersInfo(a, r) } func TestPrometheusCollector_scrapeFailure(t *testing.T) { provider := &erroringSubcontainersInfoProvider{ successfulProvider: testSubcontainersInfoProvider{}, shouldFail: true, } c := NewPrometheusCollector(provider, func(container *info.ContainerInfo) map[string]string { s := DefaultContainerLabels(container) s["zone.name"] = "hello" return s }) prometheus.MustRegister(c) defer prometheus.Unregister(c) testPrometheusCollector(t, c, "testdata/prometheus_metrics_failure") provider.shouldFail = false testPrometheusCollector(t, c, "testdata/prometheus_metrics") } cadvisor-0.27.1/metrics/testdata/000077500000000000000000000000001315410276000166715ustar00rootroot00000000000000cadvisor-0.27.1/metrics/testdata/prometheus_metrics000066400000000000000000000573601315410276000225500ustar00rootroot00000000000000# HELP cadvisor_version_info A metric with a constant '1' value labeled by kernel version, OS version, docker version, cadvisor version & cadvisor revision. # TYPE cadvisor_version_info gauge cadvisor_version_info{cadvisorRevision="abcdef",cadvisorVersion="0.16.0",dockerVersion="1.8.1",kernelVersion="4.1.6-200.fc22.x86_64",osVersion="Fedora 22 (Twenty Two)"} 1 # HELP container_cpu_cfs_periods_total Number of elapsed enforcement period intervals. # TYPE container_cpu_cfs_periods_total counter container_cpu_cfs_periods_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 723 # HELP container_cpu_cfs_throttled_periods_total Number of throttled period intervals. # TYPE container_cpu_cfs_throttled_periods_total counter container_cpu_cfs_throttled_periods_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 18 # HELP container_cpu_cfs_throttled_seconds_total Total time duration the container has been throttled. # TYPE container_cpu_cfs_throttled_seconds_total counter container_cpu_cfs_throttled_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 1.724314 # HELP container_cpu_load_average_10s Value of container cpu load average over the last 10 seconds. # TYPE container_cpu_load_average_10s gauge container_cpu_load_average_10s{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 2 # HELP container_cpu_system_seconds_total Cumulative system cpu time consumed in seconds. # TYPE container_cpu_system_seconds_total counter container_cpu_system_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 7e-09 # HELP container_cpu_usage_seconds_total Cumulative cpu time consumed per cpu in seconds. # TYPE container_cpu_usage_seconds_total counter container_cpu_usage_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",cpu="cpu00",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 2e-09 container_cpu_usage_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",cpu="cpu01",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 3e-09 container_cpu_usage_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",cpu="cpu02",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 4e-09 container_cpu_usage_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",cpu="cpu03",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 5e-09 # HELP container_cpu_user_seconds_total Cumulative user cpu time consumed in seconds. # TYPE container_cpu_user_seconds_total counter container_cpu_user_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 6e-09 # HELP container_fs_inodes_free Number of available Inodes # TYPE container_fs_inodes_free gauge container_fs_inodes_free{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 524288 container_fs_inodes_free{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 262144 # HELP container_fs_inodes_total Number of Inodes # TYPE container_fs_inodes_total gauge container_fs_inodes_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 2.097152e+06 container_fs_inodes_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 2.097152e+06 # HELP container_fs_io_current Number of I/Os currently in progress # TYPE container_fs_io_current gauge container_fs_io_current{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 42 container_fs_io_current{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 47 # HELP container_fs_io_time_seconds_total Cumulative count of seconds spent doing I/Os # TYPE container_fs_io_time_seconds_total counter container_fs_io_time_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 4.3e-08 container_fs_io_time_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 4.8e-08 # HELP container_fs_io_time_weighted_seconds_total Cumulative weighted I/O time in seconds # TYPE container_fs_io_time_weighted_seconds_total counter container_fs_io_time_weighted_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 4.4e-08 container_fs_io_time_weighted_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 4.9e-08 # HELP container_fs_limit_bytes Number of bytes that can be consumed by the container on this filesystem. # TYPE container_fs_limit_bytes gauge container_fs_limit_bytes{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 22 container_fs_limit_bytes{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 37 # HELP container_fs_read_seconds_total Cumulative count of seconds spent reading # TYPE container_fs_read_seconds_total counter container_fs_read_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 2.7e-08 container_fs_read_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 4.2e-08 # HELP container_fs_reads_merged_total Cumulative count of reads merged # TYPE container_fs_reads_merged_total counter container_fs_reads_merged_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 25 container_fs_reads_merged_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 40 # HELP container_fs_reads_total Cumulative count of reads completed # TYPE container_fs_reads_total counter container_fs_reads_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 24 container_fs_reads_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 39 # HELP container_fs_sector_reads_total Cumulative count of sector reads completed # TYPE container_fs_sector_reads_total counter container_fs_sector_reads_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 26 container_fs_sector_reads_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 41 # HELP container_fs_sector_writes_total Cumulative count of sector writes completed # TYPE container_fs_sector_writes_total counter container_fs_sector_writes_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 40 container_fs_sector_writes_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 45 # HELP container_fs_usage_bytes Number of bytes that are consumed by the container on this filesystem. # TYPE container_fs_usage_bytes gauge container_fs_usage_bytes{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 23 container_fs_usage_bytes{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 38 # HELP container_fs_write_seconds_total Cumulative count of seconds spent writing # TYPE container_fs_write_seconds_total counter container_fs_write_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 4.1e-08 container_fs_write_seconds_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 4.6e-08 # HELP container_fs_writes_merged_total Cumulative count of writes merged # TYPE container_fs_writes_merged_total counter container_fs_writes_merged_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 39 container_fs_writes_merged_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 44 # HELP container_fs_writes_total Cumulative count of writes completed # TYPE container_fs_writes_total counter container_fs_writes_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda1",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 28 container_fs_writes_total{container_env_foo_env="prod",container_label_foo_label="bar",device="sda2",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 43 # HELP container_last_seen Last time a container was seen by the exporter # TYPE container_last_seen gauge container_last_seen{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 1.426203694e+09 # HELP container_memory_cache Number of bytes of page cache memory. # TYPE container_memory_cache gauge container_memory_cache{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 14 # HELP container_memory_failcnt Number of memory usage hits limits # TYPE container_memory_failcnt counter container_memory_failcnt{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 0 # HELP container_memory_failures_total Cumulative count of memory allocation failures. # TYPE container_memory_failures_total counter container_memory_failures_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",scope="container",type="pgfault",zone_name="hello"} 10 container_memory_failures_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",scope="container",type="pgmajfault",zone_name="hello"} 11 container_memory_failures_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",scope="hierarchy",type="pgfault",zone_name="hello"} 12 container_memory_failures_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",scope="hierarchy",type="pgmajfault",zone_name="hello"} 13 # HELP container_memory_rss Size of RSS in bytes. # TYPE container_memory_rss gauge container_memory_rss{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 15 # HELP container_memory_swap Container swap usage in bytes. # TYPE container_memory_swap gauge container_memory_swap{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 8192 # HELP container_memory_usage_bytes Current memory usage in bytes. # TYPE container_memory_usage_bytes gauge container_memory_usage_bytes{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 8 # HELP container_memory_working_set_bytes Current working set in bytes. # TYPE container_memory_working_set_bytes gauge container_memory_working_set_bytes{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 9 # HELP container_network_receive_bytes_total Cumulative count of bytes received # TYPE container_network_receive_bytes_total counter container_network_receive_bytes_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",interface="eth0",name="testcontaineralias",zone_name="hello"} 14 # HELP container_network_receive_errors_total Cumulative count of errors encountered while receiving # TYPE container_network_receive_errors_total counter container_network_receive_errors_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",interface="eth0",name="testcontaineralias",zone_name="hello"} 16 # HELP container_network_receive_packets_dropped_total Cumulative count of packets dropped while receiving # TYPE container_network_receive_packets_dropped_total counter container_network_receive_packets_dropped_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",interface="eth0",name="testcontaineralias",zone_name="hello"} 17 # HELP container_network_receive_packets_total Cumulative count of packets received # TYPE container_network_receive_packets_total counter container_network_receive_packets_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",interface="eth0",name="testcontaineralias",zone_name="hello"} 15 # HELP container_network_tcp_usage_total tcp connection usage statistic for container # TYPE container_network_tcp_usage_total gauge container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="close",zone_name="hello"} 0 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="closewait",zone_name="hello"} 0 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="closing",zone_name="hello"} 0 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="established",zone_name="hello"} 13 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="finwait1",zone_name="hello"} 0 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="finwait2",zone_name="hello"} 0 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="lastack",zone_name="hello"} 0 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="listen",zone_name="hello"} 3 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="synrecv",zone_name="hello"} 0 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="synsent",zone_name="hello"} 0 container_network_tcp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",tcp_state="timewait",zone_name="hello"} 0 # HELP container_network_transmit_bytes_total Cumulative count of bytes transmitted # TYPE container_network_transmit_bytes_total counter container_network_transmit_bytes_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",interface="eth0",name="testcontaineralias",zone_name="hello"} 18 # HELP container_network_transmit_errors_total Cumulative count of errors encountered while transmitting # TYPE container_network_transmit_errors_total counter container_network_transmit_errors_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",interface="eth0",name="testcontaineralias",zone_name="hello"} 20 # HELP container_network_transmit_packets_dropped_total Cumulative count of packets dropped while transmitting # TYPE container_network_transmit_packets_dropped_total counter container_network_transmit_packets_dropped_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",interface="eth0",name="testcontaineralias",zone_name="hello"} 21 # HELP container_network_transmit_packets_total Cumulative count of packets transmitted # TYPE container_network_transmit_packets_total counter container_network_transmit_packets_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",interface="eth0",name="testcontaineralias",zone_name="hello"} 19 # HELP container_network_udp_usage_total udp connection usage statistic for container # TYPE container_network_udp_usage_total gauge container_network_udp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",udp_state="dropped",zone_name="hello"} 0 container_network_udp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",udp_state="listen",zone_name="hello"} 0 container_network_udp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",udp_state="rxqueued",zone_name="hello"} 0 container_network_udp_usage_total{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",udp_state="txqueued",zone_name="hello"} 0 # HELP container_scrape_error 1 if there was an error while getting container metrics, 0 otherwise # TYPE container_scrape_error gauge container_scrape_error 0 # HELP container_spec_cpu_period CPU period of the container. # TYPE container_spec_cpu_period gauge container_spec_cpu_period{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 100000 # HELP container_spec_cpu_quota CPU quota of the container. # TYPE container_spec_cpu_quota gauge container_spec_cpu_quota{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 10000 # HELP container_spec_cpu_shares CPU share of the container. # TYPE container_spec_cpu_shares gauge container_spec_cpu_shares{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 1000 # HELP container_start_time_seconds Start time of the container since unix epoch in seconds. # TYPE container_start_time_seconds gauge container_start_time_seconds{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",zone_name="hello"} 1.257894e+09 # HELP container_tasks_state Number of tasks in given state # TYPE container_tasks_state gauge container_tasks_state{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",state="iowaiting",zone_name="hello"} 54 container_tasks_state{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",state="running",zone_name="hello"} 51 container_tasks_state{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",state="sleeping",zone_name="hello"} 50 container_tasks_state{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",state="stopped",zone_name="hello"} 52 container_tasks_state{container_env_foo_env="prod",container_label_foo_label="bar",id="testcontainer",image="test",name="testcontaineralias",state="uninterruptible",zone_name="hello"} 53 # HELP http_request_duration_microseconds The HTTP request latencies in microseconds. # TYPE http_request_duration_microseconds summary http_request_duration_microseconds{handler="prometheus",quantile="0.5"} 0 http_request_duration_microseconds{handler="prometheus",quantile="0.9"} 0 http_request_duration_microseconds{handler="prometheus",quantile="0.99"} 0 http_request_duration_microseconds_sum{handler="prometheus"} 0 http_request_duration_microseconds_count{handler="prometheus"} 0 # HELP http_request_size_bytes The HTTP request sizes in bytes. # TYPE http_request_size_bytes summary http_request_size_bytes{handler="prometheus",quantile="0.5"} 0 http_request_size_bytes{handler="prometheus",quantile="0.9"} 0 http_request_size_bytes{handler="prometheus",quantile="0.99"} 0 http_request_size_bytes_sum{handler="prometheus"} 0 http_request_size_bytes_count{handler="prometheus"} 0 # HELP http_response_size_bytes The HTTP response sizes in bytes. # TYPE http_response_size_bytes summary http_response_size_bytes{handler="prometheus",quantile="0.5"} 0 http_response_size_bytes{handler="prometheus",quantile="0.9"} 0 http_response_size_bytes{handler="prometheus",quantile="0.99"} 0 http_response_size_bytes_sum{handler="prometheus"} 0 http_response_size_bytes_count{handler="prometheus"} 0 # HELP machine_cpu_cores Number of CPU cores on the machine. # TYPE machine_cpu_cores gauge machine_cpu_cores 4 # HELP machine_memory_bytes Amount of memory installed on the machine. # TYPE machine_memory_bytes gauge machine_memory_bytes 1024 # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. # TYPE process_cpu_seconds_total counter process_cpu_seconds_total 0 # HELP process_goroutines Number of goroutines that currently exist. # TYPE process_goroutines gauge process_goroutines 16 # HELP process_max_fds Maximum number of open file descriptors. # TYPE process_max_fds gauge process_max_fds 1024 # HELP process_open_fds Number of open file descriptors. # TYPE process_open_fds gauge process_open_fds 4 # HELP process_resident_memory_bytes Resident memory size in bytes. # TYPE process_resident_memory_bytes gauge process_resident_memory_bytes 7.74144e+06 # HELP process_start_time_seconds Start time of the process since unix epoch in seconds. # TYPE process_start_time_seconds gauge process_start_time_seconds 1.42620369439e+09 # HELP process_virtual_memory_bytes Virtual memory size in bytes. # TYPE process_virtual_memory_bytes gauge process_virtual_memory_bytes 1.16420608e+08 cadvisor-0.27.1/metrics/testdata/prometheus_metrics_failure000066400000000000000000000002401315410276000242400ustar00rootroot00000000000000# HELP container_scrape_error 1 if there was an error while getting container metrics, 0 otherwise # TYPE container_scrape_error gauge container_scrape_error 1 cadvisor-0.27.1/pages/000077500000000000000000000000001315410276000145115ustar00rootroot00000000000000cadvisor-0.27.1/pages/assets/000077500000000000000000000000001315410276000160135ustar00rootroot00000000000000cadvisor-0.27.1/pages/assets/html/000077500000000000000000000000001315410276000167575ustar00rootroot00000000000000cadvisor-0.27.1/pages/assets/html/containers.html000066400000000000000000000224751315410276000220240ustar00rootroot00000000000000 cAdvisor - {{.DisplayName}}
{{if .IsRoot}} {{end}} {{if .Subcontainers}}
{{range $subcontainer := .Subcontainers}} {{$subcontainer.Text}} {{end}}
{{end}} {{if .DockerStatus}}
    {{range $dockerstatus := .DockerStatus}}
  • {{$dockerstatus.Key}} {{$dockerstatus.Value}}
  • {{end}} {{if .DockerDriverStatus}}
  • Storage
      {{range $driverstatus := .DockerDriverStatus}}
    • {{$driverstatus.Key}} {{$driverstatus.Value}}
    • {{end}}
{{end}}
{{end}} {{if .DockerImages}}


{{end}} {{if .ResourcesAvailable}}
{{if .CpuAvailable}}
  • CPU
  • {{if .Spec.Cpu.Limit}}
  • Shares {{printShares .Spec.Cpu.Limit}} shares
  • {{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 {{printSize .Spec.Memory.Reservation}} {{printUnit .Spec.Memory.Reservation}}
  • {{end}} {{if .Spec.Memory.Limit}}
  • Limit {{printSize .Spec.Memory.Limit}} {{printUnit .Spec.Memory.Limit}}
  • {{end}} {{if .Spec.Memory.SwapLimit}}
  • Swap Limit {{printSize .Spec.Memory.SwapLimit}} {{printUnit .Spec.Memory.SwapLimit}}
  • {{end}}
{{end}}

Overview

Processes

{{if .CpuAvailable}}

CPU

Total Usage

Usage per Core

Usage Breakdown

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

Memory

Total Usage


Usage Breakdown

Hot Memory
Cold Memory
{{end}} {{if .NetworkAvailable}}

Network

Throughput

Errors

{{end}} {{if .FsAvailable}}

Filesystem

{{end}} {{if .CustomMetricsAvailable}}

Application Metrics

{{end}} {{if .SubcontainersAvailable}}

Subcontainers

Top CPU Usage:

Top Memory Usage:

{{end}}
{{end}}
cadvisor-0.27.1/pages/assets/js/000077500000000000000000000000001315410276000164275ustar00rootroot00000000000000cadvisor-0.27.1/pages/assets/js/containers.js000066400000000000000000001031021315410276000211270ustar00rootroot00000000000000// 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. function humanize(num, size, units) { var unit; for (unit = units.pop(); units.length && num >= size; unit = units.pop()) { num /= size; } return [num, unit]; } // Following the IEC naming convention function humanizeIEC(num) { var ret = humanize(num, 1024, ['TiB', 'GiB', 'MiB', 'KiB', 'B']); return ret[0].toFixed(2) + ' ' + ret[1]; } // Following the Metric naming convention function humanizeMetric(num) { var ret = humanize(num, 1000, ['TB', 'GB', 'MB', 'KB', 'Bytes']); return ret[0].toFixed(2) + ' ' + ret[1]; } // Draw a table. function drawTable( seriesTitles, titleTypes, data, elementId, numPages, sortIndex) { var dataTable = new google.visualization.DataTable(); for (var i = 0; i < seriesTitles.length; i++) { dataTable.addColumn(titleTypes[i], seriesTitles[i]); } dataTable.addRows(data); if (!(elementId in window.charts)) { window.charts[elementId] = new google.visualization.Table(document.getElementById(elementId)); } var cssClassNames = { 'headerRow': '', 'tableRow': 'table-row', 'oddTableRow': 'table-row' }; var opts = { alternatingRowStyle: true, page: 'enable', pageSize: numPages, allowHtml: true, sortColumn: sortIndex, sortAscending: false, cssClassNames: cssClassNames }; window.charts[elementId].draw(dataTable, opts); } // Draw a line chart. function drawLineChart(seriesTitles, data, elementId, unit) { var min = Infinity; var max = -Infinity; for (var i = 0; i < data.length; i++) { // Convert the first column to a Date. if (data[i] != null) { data[i][0] = new Date(data[i][0]); } // Find min, max. for (var j = 1; j < data[i].length; j++) { var val = data[i][j]; if (val < min) { min = val; } if (val > max) { max = val; } } } // We don't want to show any values less than 0 so cap the min value at that. // At the same time, show 10% of the graph below the min value if we can. var minWindow = min - (max - min) / 10; if (minWindow < 0) { minWindow = 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. if (!(elementId in window.charts)) { window.charts[elementId] = new google.visualization.LineChart(document.getElementById(elementId)); } // TODO(vmarmol): Look into changing the view window to get a smoother // animation. var opts = { curveType: 'function', height: 300, legend: {position: 'none'}, focusTarget: 'category', vAxis: { title: unit, viewWindow: { min: minWindow, } }, legend: { position: 'bottom' } }; // If the whole data series has the same value, try to center it in the chart. if (min == max) { opts.vAxis.viewWindow.max = 1.1 * max; opts.vAxis.viewWindow.min = 0.9 * max; } window.charts[elementId].draw(dataTable, opts); } // Gets the length of the interval in nanoseconds. function getInterval(current, previous) { var cur = new Date(current); var prev = new Date(previous); // ms -> ns. return (cur.getTime() - prev.getTime()) * 1000000; } // Checks if the specified stats include the specified resource. function hasResource(stats, resource) { return stats.stats.length > 0 && stats.stats[0][resource]; } // Checks if all containers in provided list include the specified resource. function hasResourceForAll(containerInfos, resource) { if (containerInfos.length === 0) { return false; } for (var i = 0; i < containerInfos.length; i++) { if (!hasResource(containerInfos[i], resource)) { return false; } } return true; } // Draw a set of gauges. Data is comprised of an array of arrays with two // elements: // a string label and a numeric value for the gauge. function drawGauges(elementId, gauges) { gauges.unshift(['Label', 'Value']); // Create and populate the data table. var data = google.visualization.arrayToDataTable(gauges); // Create and draw the visualization. var options = { height: 100, 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(rootDir, callback) { $.getJSON(rootDir + 'api/v1.0/machine', function(data) { callback(data); }); } // Get ps info. function getProcessInfo(rootDir, containerName, callback) { $.getJSON(rootDir + 'api/v2.0/ps' + containerName) .done(function(data) { callback(data); }) .fail(function(jqhxr, textStatus, error) { callback([]); }); } // Get the container stats for the specified container. function getStats(rootDir, containerName, callback) { // Request 60s of container history and no samples. var request = JSON.stringify({ // Update main.statsRequestedByUI while updating "num_stats" here. 'num_stats': 60, 'num_samples': 0 }); $.when( $.post(rootDir + 'api/v1.0/containers' + containerName, request), $.post(rootDir + 'api/v1.1/subcontainers' + containerName, request)) .done(function(containersResp, subcontainersResp) { callback(containersResp[0], subcontainersResp[0]); }); } // Draw the graph for CPU usage. function drawCpuTotalUsage(elementId, machineInfo, stats) { if (stats.spec.has_cpu && !hasResource(stats, 'cpu')) { return; } 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]; var intervalNs = getInterval(cur.timestamp, prev.timestamp); var elements = []; elements.push(cur.timestamp); elements.push((cur.cpu.usage.total - prev.cpu.usage.total) / intervalNs); data.push(elements); } drawLineChart(titles, data, elementId, 'Cores'); } // Draw the graph for CPU load. function drawCpuLoad(elementId, machineInfo, stats) { var titles = ['Time', 'Average']; var data = []; for (var i = 1; i < stats.stats.length; i++) { var cur = stats.stats[i]; var elements = []; elements.push(cur.timestamp); elements.push(cur.cpu.load_average / 1000); data.push(elements); } drawLineChart(titles, data, elementId, 'Runnable threads'); } // Draw the graph for per-core CPU usage. function drawCpuPerCoreUsage(elementId, machineInfo, stats) { if (stats.spec.has_cpu && !hasResource(stats, 'cpu')) { return; } // 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 intervalNs = getInterval(cur.timestamp, prev.timestamp); var elements = []; elements.push(cur.timestamp); for (var j = 0; j < machineInfo.num_cores; j++) { elements.push( (cur.cpu.usage.per_cpu_usage[j] - prev.cpu.usage.per_cpu_usage[j]) / intervalNs); } data.push(elements); } drawLineChart(titles, data, elementId, 'Cores'); } // Draw the graph for CPU usage breakdown. function drawCpuUsageBreakdown(elementId, machineInfo, containerInfo) { if (containerInfo.spec.has_cpu && !hasResource(containerInfo, 'cpu')) { return; } 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]; var intervalNs = getInterval(cur.timestamp, prev.timestamp); var elements = []; elements.push(cur.timestamp); elements.push((cur.cpu.usage.user - prev.cpu.usage.user) / intervalNs); elements.push( (cur.cpu.usage.system - prev.cpu.usage.system) / intervalNs); data.push(elements); } drawLineChart(titles, data, elementId, 'Cores'); } // Return chart titles and data from an array of subcontainerInfos, using the // passed dataFn to return the individual data points. function getSubcontainerChartData(subcontainerInfos, dataFn) { var titles = ['Time']; var data = []; for (var i = 0; i < subcontainerInfos.length; i++) { titles.push(subcontainerInfos[i].name); for (var j = 1; j < subcontainerInfos[i].stats.length; j++) { var cur = subcontainerInfos[i].stats[j]; var prev = subcontainerInfos[i].stats[j - 1]; // Generate a sparse array with timestamp at index zero and data point at // index i+1, to be used as a DataTable row. var elements = [cur.timestamp]; subcontainerInfos.forEach(function() { elements.push(null); }); elements[i+1] = dataFn(cur, prev); data.push(elements); } } return { titles: titles, data: data, } } // Draw the graph for per-subcontainer CPU usage. function drawCpuPerSubcontainerUsage(elementId, subcontainerInfos) { if (!hasResourceForAll(subcontainerInfos, 'cpu')) { return; } var chartData = getSubcontainerChartData(subcontainerInfos, function(cur, prev) { var intervalNs = getInterval(cur.timestamp, prev.timestamp); return (cur.cpu.usage.total - prev.cpu.usage.total) / intervalNs; }); drawLineChart(chartData.titles, chartData.data, elementId, 'Cores'); } // Draw the graph for per-subcontainer memory usage. function drawMemoryPerSubcontainerUsage(elementId, subcontainerInfos) { if (!hasResourceForAll(subcontainerInfos, 'memory')) { return; } var chartData = getSubcontainerChartData(subcontainerInfos, function(cur, prev) { return cur.memory.usage / oneMegabyte; }); drawLineChart(chartData.titles, chartData.data, elementId, 'Megabytes'); } // Draw the gauges for overall resource usage. function drawOverallUsage(elementId, machineInfo, containerInfo) { var cur = containerInfo.stats[containerInfo.stats.length - 1]; var gauges = []; var cpuUsage = 0; if (containerInfo.spec.has_cpu && containerInfo.stats.length >= 2) { var prev = containerInfo.stats[containerInfo.stats.length - 2]; var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total; var intervalNs = getInterval(cur.timestamp, prev.timestamp); // Convert to millicores and take the percentage cpuUsage = Math.round(((rawUsage / intervalNs) / machineInfo.num_cores) * 100); if (cpuUsage > 100) { cpuUsage = 100; } gauges.push(['CPU', cpuUsage]); } var memoryUsage = 0; if (containerInfo.spec.has_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); gauges.push(['Memory', memoryUsage]); } var numGauges = gauges.length; if (cur.filesystem) { for (var i = 0; i < cur.filesystem.length; i++) { var data = cur.filesystem[i]; var totalUsage = Math.floor((data.usage * 100.0) / data.capacity); var els = window.cadvisor.fsUsage.elements[data.device]; // Update the gauges in the right order. gauges[numGauges + els.index] = ['FS #' + (els.index + 1), totalUsage]; } // Limit the number of filesystem gauges displayed to 5. // 'Filesystem details' section still shows information for all filesystems. var max_gauges = numGauges + 5; if (gauges.length > max_gauges) { gauges = gauges.slice(0, max_gauges); } } drawGauges(elementId, gauges); } var oneMegabyte = 1024 * 1024; var oneGigabyte = 1024 * oneMegabyte; function drawMemoryUsage(elementId, machineInfo, containerInfo) { if (containerInfo.spec.has_memory && !hasResource(containerInfo, 'memory')) { return; } var titles = ['Time', 'Total', 'Hot']; var data = []; for (var i = 0; i < containerInfo.stats.length; i++) { var cur = containerInfo.stats[i]; var elements = []; elements.push(cur.timestamp); elements.push(cur.memory.usage / oneMegabyte); elements.push(cur.memory.working_set / oneMegabyte); data.push(elements); } // Get the memory limit, saturate to the machine size. var memory_limit = machineInfo.memory_capacity; if (containerInfo.spec.memory.limit && (containerInfo.spec.memory.limit < memory_limit)) { memory_limit = containerInfo.spec.memory.limit; } // Updating the progress bar. var cur = containerInfo.stats[containerInfo.stats.length - 1]; var hotMemory = Math.floor((cur.memory.working_set * 100.0) / memory_limit); var totalMemory = Math.floor((cur.memory.usage * 100.0) / memory_limit); var coldMemory = totalMemory - hotMemory; $('#progress-hot-memory').width(hotMemory + '%'); $('#progress-cold-memory').width(coldMemory + '%'); $('#memory-text') .text( humanizeIEC(cur.memory.usage) + ' / ' + humanizeIEC(memory_limit) + ' (' + totalMemory + '%)'); drawLineChart(titles, data, elementId, 'Megabytes'); } // Get the index of the interface with the specified name. function getNetworkInterfaceIndex(interfaceName, interfaces) { for (var i = 0; i < interfaces.length; i++) { if (interfaces[i].name == interfaceName) { return i; } } return -1; } // Draw the graph for network tx/rx bytes. function drawNetworkBytes(elementId, machineInfo, stats) { if (stats.spec.has_network && !hasResource(stats, 'network')) { return; } // Get interface index. var interfaceIndex = -1; if (stats.stats.length > 0) { interfaceIndex = getNetworkInterfaceIndex( window.cadvisor.network.interface, stats.stats[0].network.interfaces); } if (interfaceIndex < 0) { console.log( 'Unable to find interface"', interfaceName, '" in ', stats.stats.network); return; } var titles = ['Time', 'Tx bytes', 'Rx bytes']; var data = []; for (var i = 1; i < stats.stats.length; i++) { var cur = stats.stats[i]; var prev = stats.stats[i - 1]; var intervalInSec = getInterval(cur.timestamp, prev.timestamp) / 1000000000; var elements = []; elements.push(cur.timestamp); elements.push( (cur.network.interfaces[interfaceIndex].tx_bytes - prev.network.interfaces[interfaceIndex].tx_bytes) / intervalInSec); elements.push( (cur.network.interfaces[interfaceIndex].rx_bytes - prev.network.interfaces[interfaceIndex].rx_bytes) / intervalInSec); data.push(elements); } drawLineChart(titles, data, elementId, 'Bytes per second'); } // Draw the graph for network errors function drawNetworkErrors(elementId, machineInfo, stats) { if (stats.spec.has_network && !hasResource(stats, 'network')) { return; } // Get interface index. var interfaceIndex = -1; if (stats.stats.length > 0) { interfaceIndex = getNetworkInterfaceIndex( window.cadvisor.network.interface, stats.stats[0].network.interfaces); } if (interfaceIndex < 0) { console.log( 'Unable to find interface"', interfaceName, '" in ', stats.stats.network); return; } var titles = ['Time', 'Tx', 'Rx']; var data = []; for (var i = 1; i < stats.stats.length; i++) { var cur = stats.stats[i]; var prev = stats.stats[i - 1]; var intervalInSec = getInterval(cur.timestamp, prev.timestamp) / 1000000000; var elements = []; elements.push(cur.timestamp); elements.push( (cur.network.interfaces[interfaceIndex].tx_errors - prev.network.interfaces[interfaceIndex].tx_errors) / intervalInSec); elements.push( (cur.network.interfaces[interfaceIndex].rx_errors - prev.network.interfaces[interfaceIndex].rx_errors) / intervalInSec); data.push(elements); } drawLineChart(titles, data, elementId, 'Errors per second'); } // Update the filesystem usage values. function drawFileSystemUsage(machineInfo, stats) { var cur = stats.stats[stats.stats.length - 1]; if (!cur.filesystem) { return; } var el = $('
'); for (var i = 0; i < cur.filesystem.length; i++) { var data = cur.filesystem[i]; var totalUsage = Math.floor((data.usage * 100.0) / data.capacity); // Update DOM elements. var els = window.cadvisor.fsUsage.elements[data.device]; els.progressElement.width(totalUsage + '%'); els.textElement.text( humanizeMetric(data.usage) + ' / ' + humanizeMetric(data.capacity) + ' (' + totalUsage + '%)'); } } function drawImages(images) { if (images == null || images.length == 0) { return; } window.charts = {}; var titles = ['Repository', 'Tags', 'ID', 'Virtual Size', 'Creation Time']; var titleTypes = ['string', 'string', 'string', 'number', 'number']; var sortIndex = 0; var data = []; for (var i = 0; i < images.length; i++) { var elements = []; var tags = []; var repos = images[i].repo_tags[0].split(':'); repos.splice(-1, 1); for (var j = 0; j < images[i].repo_tags.length; j++) { var splits = images[i].repo_tags[j].split(':'); if (splits.length > 1) { tags.push(splits[splits.length - 1]); } } elements.push(repos.join(':')); elements.push(tags.join(', ')); elements.push(images[i].id.substr(0, 24)); elements.push( {v: images[i].virtual_size, f: humanizeIEC(images[i].virtual_size)}); var d = new Date(images[i].created * 1000); elements.push({v: images[i].created, f: d.toLocaleString()}); data.push(elements); } drawTable(titles, titleTypes, data, 'docker-images', 30, sortIndex); } function drawProcesses(isRoot, rootDir, processInfo) { if (processInfo.length == 0) { $('#processes-top').text('No processes found'); return; } var titles = [ 'User', 'PID', 'PPID', 'Start Time', 'CPU %', 'MEM %', 'RSS', 'Virtual Size', 'Status', 'Running Time', 'Command' ]; var titleTypes = [ 'string', 'number', 'number', 'string', 'number', 'number', 'number', 'number', 'string', 'string', 'string' ]; var sortIndex = 4; if (isRoot) { titles.push('Container'); titleTypes.push('string'); } var data = []; for (var i = 0; i < processInfo.length; i++) { var elements = []; elements.push(processInfo[i].user); elements.push(processInfo[i].pid); elements.push(processInfo[i].parent_pid); elements.push(processInfo[i].start_time); elements.push({ v: processInfo[i].percent_cpu, f: processInfo[i].percent_cpu.toFixed(2) }); elements.push({ v: processInfo[i].percent_mem, f: processInfo[i].percent_mem.toFixed(2) }); elements.push({v: processInfo[i].rss, f: humanizeIEC(processInfo[i].rss)}); elements.push({ v: processInfo[i].virtual_size, f: humanizeIEC(processInfo[i].virtual_size) }); elements.push(processInfo[i].status); elements.push(processInfo[i].running_time); elements.push(processInfo[i].cmd); if (isRoot) { var cgroup = processInfo[i].cgroup_path; // Use the raw cgroup link as it works for all containers. var cgroupLink = '' + cgroup.substr(0, 30) + ' '; elements.push({v: cgroup, f: cgroupLink}); } data.push(elements); } drawTable(titles, titleTypes, data, 'processes-top', 25, sortIndex); } // Draw the filesystem usage nodes. function startFileSystemUsage(elementId, machineInfo, stats) { window.cadvisor.fsUsage = {}; // A map of device name to DOM elements. window.cadvisor.fsUsage.elements = {}; var cur = stats.stats[stats.stats.length - 1]; var el = $('
'); if (!cur.filesystem) { return; } for (var i = 0; i < cur.filesystem.length; i++) { var data = cur.filesystem[i]; el.append( $('
') .addClass('row col-sm-12') .append($('

').text('FS #' + (i + 1) + ': ' + data.device))); var progressElement = $('
').addClass('progress-bar progress-bar-danger'); el.append( $('
') .addClass('col-sm-9') .append($('
').addClass('progress').append(progressElement))); var textElement = $('
').addClass('col-sm-3'); el.append(textElement); window.cadvisor.fsUsage.elements[data.device] = { 'progressElement': progressElement, 'textElement': textElement, 'index': i, }; } $('#' + elementId).empty().append(el); drawFileSystemUsage(machineInfo, stats); } // 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, subcontainers) { var steps = []; if (containerInfo.spec.has_cpu || containerInfo.spec.has_memory) { steps.push(function() { drawOverallUsage('usage-gauge', machineInfo, containerInfo); }); } // CPU. if (containerInfo.spec.has_cpu) { steps.push(function() { drawCpuTotalUsage('cpu-total-usage-chart', machineInfo, containerInfo); }); // TODO(rjnagal): Re-enable CPU Load after understanding resource usage. // steps.push(function() { // drawCpuLoad("cpu-load-chart", machineInfo, containerInfo); // }); steps.push(function() { drawCpuPerCoreUsage( 'cpu-per-core-usage-chart', machineInfo, containerInfo); }); steps.push(function() { drawCpuUsageBreakdown( 'cpu-usage-breakdown-chart', machineInfo, containerInfo); }); } // Memory. if (containerInfo.spec.has_memory) { steps.push(function() { drawMemoryUsage('memory-usage-chart', machineInfo, containerInfo); }); } // Network. if (containerInfo.spec.has_network) { steps.push(function() { drawNetworkBytes('network-bytes-chart', machineInfo, containerInfo); }); steps.push(function() { drawNetworkErrors('network-errors-chart', machineInfo, containerInfo); }); } // Filesystem. if (containerInfo.spec.has_filesystem) { steps.push(function() { drawFileSystemUsage(machineInfo, containerInfo); }); } // Custom Metrics if (containerInfo.spec.has_custom_metrics) { steps.push(function() { getCustomMetrics( window.cadvisor.rootDir, window.cadvisor.containerName, function(metricsInfo) { drawCustomMetrics( 'custom-metrics-chart', containerInfo, metricsInfo); }); }); } // Subcontainers. var subcontainerInfos = filterSubcontainers(containerInfo, subcontainers); if (subcontainerInfos.length > 0) { if (hasResourceForAll(subcontainerInfos, 'cpu')) { steps.push(function() { var displayCount = $('#cpu-per-subcontainer-display-count').val(); drawCpuPerSubcontainerUsage( 'cpu-per-subcontainer-usage-chart', sliceByCpu(subcontainerInfos, displayCount)); }); } if (hasResourceForAll(subcontainerInfos, 'memory')) { steps.push(function() { var displayCount = $('#memory-per-subcontainer-display-count').val(); drawMemoryPerSubcontainerUsage( 'memory-per-subcontainer-usage-chart', sliceByMemory(subcontainerInfos, displayCount)); }); } } stepExecute(steps); } // Return an slice of subcontainers sorted by CPU, with at most 'count' entries. function sliceByCpu(subcontainerInfos, count) { subcontainerInfos.sort(function(a, b) { if (a.averages.cpu > b.averages.cpu) { return -1; } else if (a.averages.cpu < b.averages.cpu) { return 1; } else { return compareByName(a, b); } }); return subcontainerInfos.slice(0, Math.min(subcontainerInfos.length, count)) .sort(compareByName); } // Return an slice of subcontainers sorted by memory, with at most 'count' // entries. function sliceByMemory(subcontainerInfos, count) { subcontainerInfos.sort(function(a, b) { if (a.averages.memory > b.averages.memory) { return -1; } else if (a.averages.memory < b.averages.memory) { return 1; } else { return compareByName(a, b); } }); return subcontainerInfos.slice(0, Math.min(subcontainerInfos.length, count)) .sort(compareByName); } // Return sort comparitor based on subcontroller name. function compareByName(subA, subB) { if (subA.name > subB.name) { return 1; } else if (subA.name < subB.name) { return -1; } else { return 0; } } // Return a map of the averages of the subcontainer stats. function getSubcontainerAverages(subcontainer) { var cpuSum = 0; var memorySum = 0; subcontainer.stats.forEach(function(stat) { cpuSum += stat.cpu.usage.total; memorySum += stat.memory.usage / oneMegabyte; }); return { cpu: cpuSum / subcontainer.stats.length, memory: memorySum / subcontainer.stats.length, } } // Return a list of immediate subcontainers, including metric averages. function filterSubcontainers(containerInfo, subcontainers) { if (!containerInfo.subcontainers || containerInfo.subcontainers.length === 0 || !subcontainers) { return []; } var subcontainerNames = {}; containerInfo.subcontainers.forEach(function(subcontainer) { subcontainerNames[subcontainer.name] = subcontainer.name; }); var subcontainerInfos = []; subcontainers.forEach(function(subcontainer) { if (subcontainerNames[subcontainer.name] !== undefined) { subcontainer.averages = getSubcontainerAverages(subcontainer); subcontainerInfos.push(subcontainer); } }); return subcontainerInfos; } function setNetwork(interfaceName) { $('#network-selection-text') .empty() .append($('').text('Interface: ')) .append($('').text(interfaceName)); window.cadvisor.network.interface = interfaceName; // Draw the new stats. refreshStats(); } // Creates the network selection dropdown. function startNetwork(selectionElement, containerInfo) { if (!hasResource(containerInfo, 'network') || containerInfo.stats.length == 0 || !containerInfo.stats[0].network.interfaces || containerInfo.stats[0].network.interfaces.length == 0) { return; } window.cadvisor.network = {}; window.cadvisor.network.interface = ''; // Add all interfaces to the dropdown. var el = $('#' + selectionElement); for (var i = 0; i < containerInfo.stats[0].network.interfaces.length; i++) { var interfaceName = containerInfo.stats[0].network.interfaces[i].name; el.append($('
  • ') .attr('role', 'presentation') .append($('') .attr('role', 'menuitem') .attr('tabindex', -1) .click(setNetwork.bind(null, interfaceName)) .text(interfaceName))); } setNetwork(containerInfo.stats[0].network.interfaces[0].name); } // Refresh the stats on the page. function refreshStats() { var machineInfo = window.cadvisor.machineInfo; getStats( window.cadvisor.rootDir, window.cadvisor.containerName, function(containerInfo, subcontainers) { if (window.cadvisor.firstRun) { window.cadvisor.firstRun = false; if (containerInfo.spec.has_filesystem) { startFileSystemUsage( 'filesystem-usage', machineInfo, containerInfo); } if (containerInfo.spec.has_network) { startNetwork('network-selection', containerInfo); } if (containerInfo.spec.has_custom_metrics) { startCustomMetrics('custom-metrics-chart', containerInfo); } } drawCharts(machineInfo, containerInfo, subcontainers); }); } function addAllLabels(containerInfo, metricsInfo) { if (metricsInfo.length == 0) { return; } var metricSpec = containerInfo.spec.custom_metrics; for (var containerName in metricsInfo) { var container = metricsInfo[containerName]; for (i = 0; i < metricSpec.length; i++) { metricName = metricSpec[i].name; metricLabelVal = container[metricName]; firstLabel = true; for (var label in metricLabelVal) { if (label == '') { $('#button-' + metricName).hide(); } $('#' + metricName + '_labels') .append( $('
  • ') .attr('role', 'presentation') .append($('') .attr('role', 'menuitem') .click(setLabel.bind(null, metricName, label)) .text(label))); if (firstLabel) { firstLabel = false; setLabel(metricName, label); } } } } } function getMetricIndex(metricName) { for (i = 0; i < window.cadvisor.metricLabelPair.length; ++i) { if (window.cadvisor.metricLabelPair[i][0] == metricName) { return i; } } return -1; } function setLabel(metric, label) { $('#' + metric + '-selection-text') .empty() .append($('').text('Label: ')) .append($('').text(label)); index = getMetricIndex(metric); if (index == -1) { window.cadvisor.metricLabelPair.push([metric, label]); } else { window.cadvisor.metricLabelPair[index][1] = label; } refreshStats(); } function getSelectedLabel(metricName) { index = getMetricIndex(metricName); if (index == -1) { return ''; } return window.cadvisor.metricLabelPair[index][1]; } function startCustomMetrics(elementId, containerInfo) { var metricSpec = containerInfo.spec.custom_metrics; var metricStats = containerInfo.stats.custom_metrics; var el = $('
    '); if (metricSpec.length < window.cadvisor.maxCustomMetrics) { window.cadvisor.maxCustomMetrics = metricSpec.length; for (i = 0; i < window.cadvisor.maxCustomMetrics; i++) { metricName = metricSpec[i].name; var divText = '
    '; divText += '
    '; divText += '
    '; el.append($(divText)); } } el.append($('
    ')); $('#' + elementId).append(el); } function getCustomMetrics(rootDir, containerName, callback) { $.getJSON(rootDir + 'api/v2.0/appmetrics/' + containerName) .done(function(data) { callback(data); }) .fail(function(jqhxr, textStatus, error) { callback([]); }); } function drawCustomMetrics(elementId, containerInfo, metricsInfo) { if (metricsInfo.length == 0) { return; } var metricSpec = containerInfo.spec.custom_metrics; for (var containerName in metricsInfo) { var container = metricsInfo[containerName]; for (i = 0; i < window.cadvisor.maxCustomMetrics; i++) { metricName = metricSpec[i].name; metricUnits = metricSpec[i].units; var titles = ['Time', metricName]; metricLabelVal = container[metricName]; if (window.cadvisor.firstCustomCollection) { window.cadvisor.firstCustomCollection = false; addAllLabels(containerInfo, metricsInfo); } var data = []; selectedLabel = getSelectedLabel(metricName); metricVal = metricLabelVal[selectedLabel]; for (var index in metricVal) { metric = metricVal[index]; var elements = []; for (var attribute in metric) { value = metric[attribute]; elements.push(value); } if (elements.length < 2) { elements.push(0); } data.push(elements); } drawLineChart(titles, data, elementId + '-' + metricName, metricUnits); } } } // Executed when the page finishes loading. function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) { // Don't fetch data if we don't have any resource. if (!hasCpu && !hasMemory) { return; } window.charts = {}; window.cadvisor = {}; window.cadvisor.firstRun = true; window.cadvisor.rootDir = rootDir; window.cadvisor.containerName = containerName; window.cadvisor.firstCustomCollection = true; window.cadvisor.metricLabelPair = []; window.cadvisor.maxCustomMetrics = 10; // Draw process information at start and refresh every 60s. getProcessInfo(rootDir, containerName, function(processInfo) { drawProcesses(isRoot, rootDir, processInfo); }); setInterval(function() { getProcessInfo(rootDir, containerName, function(processInfo) { drawProcesses(isRoot, rootDir, processInfo); }); }, 60000); // Get machine info, then get the stats every 1s. getMachineInfo(rootDir, function(machineInfo) { window.cadvisor.machineInfo = machineInfo; setInterval(function() { refreshStats(); }, 1000); }); } cadvisor-0.27.1/pages/assets/styles/000077500000000000000000000000001315410276000173365ustar00rootroot00000000000000cadvisor-0.27.1/pages/assets/styles/containers.css000066400000000000000000004033511315410276000222230ustar00rootroot00000000000000 .google-visualization-toolbar{font-size:100%}.google-visualization-toolbar .google-visualization-toolbar-export-igoogle,.google-visualization-toolbar .google-visualization-toolbar-export-data,.google-visualization-toolbar .google-visualization-toolbar-html-code{margin-right:.1em}.google-visualization-toolbar-html-code-explanation{font-weight:bold}.google-visualization-toolbar-ok-button{padding:2px}.google-visualization-toolbar-triangle{position:absolute;right:0;top:0}.google-visualization-toolbar-caption-table{width:100%;padding:0;margin:0;border:0;border-collapse:collapse}.google-visualization-toolbar-small-dialog{width:500px}.google-visualization-toolbar-big-dialog{width:800px}.google-visualization-toolbar-small-dialog,.google-visualization-toolbar-big-dialog{position:absolute;background-color:#c1d9ff;border:1px solid #3a5774;padding:8px}.google-visualization-toolbar-small-dialog-bg,.google-visualization-toolbar-big-dialog-bg{background-color:#ddd;position:absolute;top:0;left:0}.google-visualization-toolbar-small-dialog-title,.google-visualization-toolbar-big-dialog-title{background-color:#e0edfe;color:#000;cursor:pointer;padding:8px;position:relative;font-size:12pt;font-weight:bold;vertical-align:middle}.google-visualization-toolbar-small-dialog-content,.google-visualization-toolbar-big-dialog-content{background-color:#fff;padding:4px;font-weight:normal;overflow:auto}.google-visualization-toolbar-small-dialog-title-close,.google-visualization-toolbar-big-dialog-title-close{background:transparent url(close_box.gif) no-repeat scroll center;height:15px;position:absolute;right:10px;top:8px;width:15px}.google-visualization-toolbar-small-dialog-content iframe,.google-visualization-toolbar-big-dialog-content iframe{width:500px;height:700px;border:1px solid black}.charts-inline-block{position:relative;display:-moz-inline-box;display:inline-block}* html .charts-inline-block,*:first-child+html .charts-inline-block{display:inline}.charts-menu{background:#fff;border-color:#ccc #666 #666 #ccc;border-style:solid;border-width:1px;cursor:default;font:normal 13px Arial,sans-serif;margin:0;outline:none;padding:4px 0;position:absolute;z-index:20000}.charts-menu-button{background:#ddd url(//ssl.gstatic.com/editor/button-bg.png) repeat-x top left;border:0;color:#000;cursor:pointer;list-style:none;margin:2px;outline:none;padding:0;text-decoration:none;vertical-align:middle}.charts-menu-button-outer-box,.charts-menu-button-inner-box{border-style:solid;border-color:#aaa;vertical-align:top}.charts-menu-button-outer-box{margin:0;border-width:1px 0;padding:0}.charts-menu-button-inner-box{margin:0 -1px;border-width:0 1px;padding:3px 4px}* html .charts-menu-button-inner-box{left:-1px}* html .charts-menu-button-rtl .charts-menu-button-outer-box{left:-1px;right:auto}* html .charts-menu-button-rtl .charts-menu-button-inner-box{right:auto}*:first-child+html .charts-menu-button-inner-box{left:-1px}*:first-child+html .charts-menu-button-rtl .charts-menu-button-inner-box{left:1px;right:auto}::root .charts-menu-button{line-height:0}::root .charts-menu-button-outer-box{line-height:0}::root .charts-menu-button-inner-box{line-height:0}::root .charts-menu-button-caption{line-height:normal}::root .charts-menu-button-dropdown{line-height:normal}.charts-menu-button-disabled{background-image:none!important;opacity:.3;-moz-opacity:.3;filter:alpha(opacity=30)}.charts-menu-button-disabled .charts-menu-button-outer-box,.charts-menu-button-disabled .charts-menu-button-inner-box,.charts-menu-button-disabled .charts-menu-button-caption,.charts-menu-button-disabled .charts-menu-button-dropdown{color:#333!important;border-color:#999!important}* html .charts-menu-button-disabled,*:first-child+html .charts-menu-button-disabled{margin:2px 1px!important;padding:0 1px!important}.charts-menu-button-hover .charts-menu-button-outer-box,.charts-menu-button-hover .charts-menu-button-inner-box{border-color:#9cf #69e #69e #7af!important}.charts-menu-button-active,.charts-menu-button-open{background-color:#bbb;background-position:bottom left}.charts-menu-button-focused .charts-menu-button-outer-box,.charts-menu-button-focused .charts-menu-button-inner-box{border-color:orange}.charts-menu-button-caption{padding:0 4px 0 0;vertical-align:top}.charts-menu-button-dropdown{height:15px;width:7px;background:url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -388px 0;vertical-align:top}.charts-menu-button-collapse-right,.charts-menu-button-collapse-right .charts-menu-button-outer-box,.charts-menu-button-collapse-right .charts-menu-button-inner-box{margin-right:0}.charts-menu-button-collapse-left,.charts-menu-button-collapse-left .charts-menu-button-outer-box{margin-left:0}.charts-menu-button-collapse-left .charts-menu-button-inner-box{margin-left:0;border-left:1px solid #fff}.charts-menu-button-collapse-left.charts-menu-button-checked .charts-menu-button-inner-box{border-left:1px solid #ddd}.charts-menuitem{color:#000;font:normal 13px Arial,sans-serif;list-style:none;margin:0;padding:4px 7em 4px 28px;white-space:nowrap}.charts-menuitem.charts-menuitem-rtl{padding-left:7em;padding-right:28px}.charts-menu-nocheckbox .charts-menuitem,.charts-menu-noicon .charts-menuitem{padding-left:12px}.charts-menu-noaccel .charts-menuitem{padding-right:20px}.charts-menuitem-content{color:#000;font:normal 13px Arial,sans-serif}.charts-menuitem-disabled .charts-menuitem-accel,.charts-menuitem-disabled .charts-menuitem-content{color:#ccc!important}.charts-menuitem-disabled .charts-menuitem-icon{opacity:.3;-moz-opacity:.3;filter:alpha(opacity=30)}.charts-menuitem-highlight,.charts-menuitem-hover{background-color:#d6e9f8;border-color:#d6e9f8;border-style:dotted;border-width:1px 0;padding-bottom:3px;padding-top:3px}.charts-menuitem-checkbox,.charts-menuitem-icon{background-repeat:no-repeat;height:16px;left:6px;position:absolute;right:auto;vertical-align:middle;width:16px}.charts-menuitem-rtl .charts-menuitem-checkbox,.charts-menuitem-rtl .charts-menuitem-icon{left:auto;right:6px}.charts-option-selected .charts-menuitem-checkbox,.charts-option-selected .charts-menuitem-icon{background:url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0}.charts-menuitem-accel{color:#999;direction:ltr;left:auto;padding:0 6px;position:absolute;right:0;text-align:right}.charts-menuitem-rtl .charts-menuitem-accel{left:0;right:auto;text-align:left}.charts-menuitem-mnemonic-hint{text-decoration:underline}.charts-menuitem-mnemonic-separator{color:#999;font-size:12px;padding-left:4px} .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; } .page-header h1 { word-wrap: break-word; } .table-row { font-family: "courier", "monospace"; font-size: 15px; text-align: right; vertical-align: top; border: 5px; margin-left: 3px; margin-right: 3px; margin-top: 3px; margin-bottom: 3px; } .subcontainer-display-input { margin-left: 4px; width: 40px; } #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="); } cadvisor-0.27.1/pages/containers.go000066400000000000000000000143001315410276000172030ustar00rootroot00000000000000// 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" "net/http" "net/url" "path" "strconv" "strings" "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/manager" "github.com/golang/glog" ) const ContainersPage = "/containers/" // from http://golang.org/doc/effective_go.html#constants type ByteSize float64 const ( _ = iota // KB - kilobyte KB ByteSize = 1 << (10 * iota) // MB - megabyte MB // GB - gigabyte GB // TB - terabyte TB // PB - petabyte PB // EB - exabyte EB // ZB - zettabyte ZB // YB - yottabyte YB ) func (b ByteSize) Size() string { for _, i := range [...]ByteSize{YB, ZB, EB, PB, TB, GB, MB, KB} { if b >= i { return fmt.Sprintf("%.2f", b/i) } } return fmt.Sprintf("%.2f", b) } func (b ByteSize) Unit() string { switch { case b >= YB: return "YB" case b >= ZB: return "ZB" case b >= EB: return "EB" case b >= PB: return "PB" case b >= TB: return "TB" case b >= GB: return "GB" case b >= MB: return "MB" case b >= KB: return "KB" } return "B" } var funcMap = template.FuncMap{ "printMask": printMask, "printCores": printCores, "printShares": printShares, "printSize": printSize, "printUnit": printUnit, } func printMask(mask string, numCores int) interface{} { masks := make([]string, numCores) activeCores := getActiveCores(mask) for i := 0; i < numCores; i++ { coreClass := "inactive-cpu" if activeCores[i] { coreClass = "active-cpu" } masks[i] = fmt.Sprintf("%d", coreClass, i) } return template.HTML(strings.Join(masks, " ")) } func getActiveCores(mask string) map[int]bool { activeCores := make(map[int]bool) for _, corebits := range strings.Split(mask, ",") { cores := strings.Split(corebits, "-") if len(cores) == 1 { index, err := strconv.Atoi(cores[0]) if err != nil { // Ignore malformed strings. continue } activeCores[index] = true } else if len(cores) == 2 { start, err := strconv.Atoi(cores[0]) if err != nil { continue } end, err := strconv.Atoi(cores[1]) if err != nil { continue } for i := start; i <= end; i++ { activeCores[i] = true } } } return activeCores } func printCores(millicores *uint64) string { cores := float64(*millicores) / 1000 return strconv.FormatFloat(cores, 'f', 3, 64) } func printShares(shares *uint64) string { return fmt.Sprintf("%d", *shares) } // Size after which we consider memory to be "unlimited". This is not // MaxInt64 due to rounding by the kernel. const maxMemorySize = uint64(1 << 62) func printSize(bytes uint64) string { if bytes >= maxMemorySize { return "unlimited" } return ByteSize(bytes).Size() } func printUnit(bytes uint64) string { if bytes >= maxMemorySize { return "" } return ByteSize(bytes).Unit() } func serveContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) { start := time.Now() // The container name is the path after the handler containerName := u.Path[len(ContainersPage)-1:] // Get the container. reqParams := info.ContainerInfoRequest{ NumStats: 60, } cont, err := m.GetContainerInfo(containerName, &reqParams) if err != nil { http.Error(w, fmt.Sprintf("failed to get container %q with error: %v", containerName, err), http.StatusNotFound) return } displayName := getContainerDisplayName(cont.ContainerReference) // Get the MachineInfo machineInfo, err := m.GetMachineInfo() if err != nil { http.Error(w, fmt.Sprintf("failed to get machine info: %v", err), http.StatusInternalServerError) return } rootDir := getRootDir(containerName) // Make a list of the parent containers and their links pathParts := strings.Split(string(cont.Name), "/") parentContainers := make([]link, 0, len(pathParts)) parentContainers = append(parentContainers, link{ Text: "root", Link: path.Join(rootDir, ContainersPage), }) for i := 1; i < len(pathParts); i++ { // Skip empty parts. if pathParts[i] == "" { continue } parentContainers = append(parentContainers, link{ Text: pathParts[i], Link: path.Join(rootDir, ContainersPage, path.Join(pathParts[1:i+1]...)), }) } // Build the links for the subcontainers. subcontainerLinks := make([]link, 0, len(cont.Subcontainers)) for _, sub := range cont.Subcontainers { if !m.Exists(sub.Name) { continue } subcontainerLinks = append(subcontainerLinks, link{ Text: getContainerDisplayName(sub), Link: path.Join(rootDir, ContainersPage, sub.Name), }) } data := &pageData{ DisplayName: displayName, ContainerName: escapeContainerName(cont.Name), ParentContainers: parentContainers, Subcontainers: subcontainerLinks, Spec: cont.Spec, Stats: cont.Stats, MachineInfo: machineInfo, IsRoot: cont.Name == "/", ResourcesAvailable: cont.Spec.HasCpu || cont.Spec.HasMemory || cont.Spec.HasNetwork || cont.Spec.HasFilesystem, CpuAvailable: cont.Spec.HasCpu, MemoryAvailable: cont.Spec.HasMemory, NetworkAvailable: cont.Spec.HasNetwork, FsAvailable: cont.Spec.HasFilesystem, CustomMetricsAvailable: cont.Spec.HasCustomMetrics, SubcontainersAvailable: len(subcontainerLinks) > 0, Root: rootDir, } err = pageTemplate.Execute(w, data) if err != nil { glog.Errorf("Failed to apply template: %s", err) } glog.V(5).Infof("Request took %s", time.Since(start)) } // Build a relative path to the root of the container page. func getRootDir(containerName string) string { // The root is at: container depth levels := (strings.Count(containerName, "/")) return strings.Repeat("../", levels) } cadvisor-0.27.1/pages/docker.go000066400000000000000000000117651315410276000163210ustar00rootroot00000000000000// 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 import ( "fmt" "net/http" "net/url" "path" "strconv" "time" "github.com/google/cadvisor/container/docker" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/manager" "github.com/golang/glog" ) const DockerPage = "/docker/" func toStatusKV(status info.DockerStatus) ([]keyVal, []keyVal) { ds := []keyVal{ {Key: "Driver", Value: status.Driver}, } for k, v := range status.DriverStatus { ds = append(ds, keyVal{Key: k, Value: v}) } return []keyVal{ {Key: "Docker Version", Value: status.Version}, {Key: "Docker API Version", Value: status.APIVersion}, {Key: "Kernel Version", Value: status.KernelVersion}, {Key: "OS Version", Value: status.OS}, {Key: "Host Name", Value: status.Hostname}, {Key: "Docker Root Directory", Value: status.RootDir}, {Key: "Execution Driver", Value: status.ExecDriver}, {Key: "Number of Images", Value: strconv.Itoa(status.NumImages)}, {Key: "Number of Containers", Value: strconv.Itoa(status.NumContainers)}, }, ds } func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) { start := time.Now() // The container name is the path after the handler containerName := u.Path[len(DockerPage)-1:] rootDir := getRootDir(containerName) var data *pageData if containerName == "/" { // Get the containers. reqParams := info.ContainerInfoRequest{ NumStats: 0, } conts, err := m.AllDockerContainers(&reqParams) if err != nil { http.Error(w, fmt.Sprintf("failed to get container %q with error: %v", containerName, err), http.StatusNotFound) return } subcontainers := make([]link, 0, len(conts)) for _, cont := range conts { subcontainers = append(subcontainers, link{ Text: getContainerDisplayName(cont.ContainerReference), Link: path.Join(rootDir, DockerPage, docker.ContainerNameToDockerId(cont.ContainerReference.Name)), }) } // Get Docker status status, err := m.DockerInfo() if err != nil { http.Error(w, fmt.Sprintf("failed to get docker info: %v", err), http.StatusInternalServerError) return } dockerStatus, driverStatus := toStatusKV(status) // Get Docker Images images, err := m.DockerImages() if err != nil { http.Error(w, fmt.Sprintf("failed to get docker images: %v", err), http.StatusInternalServerError) return } dockerContainersText := "Docker Containers" data = &pageData{ DisplayName: dockerContainersText, ParentContainers: []link{ { Text: dockerContainersText, Link: path.Join(rootDir, DockerPage), }}, Subcontainers: subcontainers, Root: rootDir, DockerStatus: dockerStatus, DockerDriverStatus: driverStatus, DockerImages: images, } } else { // Get the container. reqParams := info.ContainerInfoRequest{ NumStats: 60, } cont, err := m.DockerContainer(containerName[1:], &reqParams) if err != nil { http.Error(w, fmt.Sprintf("failed to get container %q with error: %v", containerName, err), http.StatusNotFound) return } displayName := getContainerDisplayName(cont.ContainerReference) // Make a list of the parent containers and their links var parentContainers []link parentContainers = append(parentContainers, link{ Text: "Docker Containers", Link: path.Join(rootDir, DockerPage), }) parentContainers = append(parentContainers, link{ Text: displayName, Link: path.Join(rootDir, DockerPage, docker.ContainerNameToDockerId(cont.Name)), }) // Get the MachineInfo machineInfo, err := m.GetMachineInfo() if err != nil { http.Error(w, fmt.Sprintf("failed to get machine info: %v", err), http.StatusInternalServerError) return } data = &pageData{ DisplayName: displayName, ContainerName: escapeContainerName(cont.Name), ParentContainers: parentContainers, Spec: cont.Spec, Stats: cont.Stats, MachineInfo: machineInfo, ResourcesAvailable: cont.Spec.HasCpu || cont.Spec.HasMemory || cont.Spec.HasNetwork, CpuAvailable: cont.Spec.HasCpu, MemoryAvailable: cont.Spec.HasMemory, NetworkAvailable: cont.Spec.HasNetwork, FsAvailable: cont.Spec.HasFilesystem, CustomMetricsAvailable: cont.Spec.HasCustomMetrics, Root: rootDir, } } err := pageTemplate.Execute(w, data) if err != nil { glog.Errorf("Failed to apply template: %s", err) } glog.V(5).Infof("Request took %s", time.Since(start)) return } cadvisor-0.27.1/pages/pages.go000066400000000000000000000115371315410276000161460ustar00rootroot00000000000000// 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 import ( "fmt" "html/template" "net/http" "net/url" "strings" httpmux "github.com/google/cadvisor/http/mux" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/manager" auth "github.com/abbot/go-http-auth" "github.com/golang/glog" ) var pageTemplate *template.Template type link struct { // Text to show in the link. Text string // Web address to link to. Link string } type keyVal struct { Key string Value string } type pageData struct { DisplayName string ContainerName string ParentContainers []link Subcontainers []link Spec info.ContainerSpec Stats []*info.ContainerStats MachineInfo *info.MachineInfo IsRoot bool ResourcesAvailable bool CpuAvailable bool MemoryAvailable bool NetworkAvailable bool FsAvailable bool CustomMetricsAvailable bool SubcontainersAvailable bool Root string DockerStatus []keyVal DockerDriverStatus []keyVal DockerImages []info.DockerImage } func init() { containersHtmlTemplate, _ := Asset("pages/assets/html/containers.html") pageTemplate = template.New("containersTemplate").Funcs(funcMap) _, err := pageTemplate.Parse(string(containersHtmlTemplate)) if err != nil { glog.Fatalf("Failed to parse template: %s", err) } } func containerHandlerNoAuth(containerManager manager.Manager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { serveContainersPage(containerManager, w, r.URL) } } func containerHandler(containerManager manager.Manager) auth.AuthenticatedHandlerFunc { return func(w http.ResponseWriter, r *auth.AuthenticatedRequest) { serveContainersPage(containerManager, w, r.URL) } } func dockerHandlerNoAuth(containerManager manager.Manager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { serveDockerPage(containerManager, w, r.URL) } } func dockerHandler(containerManager manager.Manager) auth.AuthenticatedHandlerFunc { return func(w http.ResponseWriter, r *auth.AuthenticatedRequest) { serveDockerPage(containerManager, w, r.URL) } } // Register http handlers func RegisterHandlersDigest(mux httpmux.Mux, containerManager manager.Manager, authenticator *auth.DigestAuth) error { // Register the handler for the containers page. if authenticator != nil { mux.HandleFunc(ContainersPage, authenticator.Wrap(containerHandler(containerManager))) mux.HandleFunc(DockerPage, authenticator.Wrap(dockerHandler(containerManager))) } else { mux.HandleFunc(ContainersPage, containerHandlerNoAuth(containerManager)) mux.HandleFunc(DockerPage, dockerHandlerNoAuth(containerManager)) } return nil } func RegisterHandlersBasic(mux httpmux.Mux, containerManager manager.Manager, authenticator *auth.BasicAuth) error { // Register the handler for the containers and docker age. if authenticator != nil { mux.HandleFunc(ContainersPage, authenticator.Wrap(containerHandler(containerManager))) mux.HandleFunc(DockerPage, authenticator.Wrap(dockerHandler(containerManager))) } else { mux.HandleFunc(ContainersPage, containerHandlerNoAuth(containerManager)) mux.HandleFunc(DockerPage, dockerHandlerNoAuth(containerManager)) } return nil } func getContainerDisplayName(cont info.ContainerReference) string { // Pick a user-added alias as display name. displayName := "" for _, alias := range cont.Aliases { // ignore container id as alias. if strings.Contains(cont.Name, alias) { continue } // pick shortest display name if multiple aliases are available. if displayName == "" || len(displayName) >= len(alias) { displayName = alias } } if displayName == "" { displayName = cont.Name } else if len(displayName) > 50 { // truncate display name to fit in one line. displayName = displayName[:50] + "..." } // Add the full container name to the display name. if displayName != cont.Name { displayName = fmt.Sprintf("%s (%s)", displayName, cont.Name) } return displayName } // Escape the non-path characters on a container name. func escapeContainerName(containerName string) string { parts := strings.Split(containerName, "/") for i := range parts { parts[i] = url.QueryEscape(parts[i]) } return strings.Join(parts, "/") } cadvisor-0.27.1/pages/static/000077500000000000000000000000001315410276000160005ustar00rootroot00000000000000cadvisor-0.27.1/pages/static/static.go000066400000000000000000000046321315410276000176230ustar00rootroot00000000000000// 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" "mime" "net/http" "net/url" "path" "github.com/golang/glog" ) const StaticResource = "/static/" var bootstrapJs, _ = Asset("pages/assets/js/bootstrap-3.1.1.min.js") var containersJs, _ = Asset("pages/assets/js/containers.js") var gchartsJs, _ = Asset("pages/assets/js/gcharts.js") var googleJsapiJs, _ = Asset("pages/assets/js/google-jsapi.js") var jqueryJs, _ = Asset("pages/assets/js/jquery-1.10.2.min.js") var bootstrapCss, _ = Asset("pages/assets/styles/bootstrap-3.1.1.min.css") var bootstrapThemeCss, _ = Asset("pages/assets/styles/bootstrap-theme-3.1.1.min.css") var containersCss, _ = Asset("pages/assets/styles/containers.css") var staticFiles = map[string][]byte{ "bootstrap-3.1.1.min.css": bootstrapCss, "bootstrap-3.1.1.min.js": bootstrapJs, "bootstrap-theme-3.1.1.min.css": bootstrapThemeCss, "containers.css": containersCss, "containers.js": containersJs, "gcharts.js": gchartsJs, "google-jsapi.js": googleJsapiJs, "jquery-1.10.2.min.js": jqueryJs, } func HandleRequest(w http.ResponseWriter, u *url.URL) { if len(u.Path) <= len(StaticResource) { http.Error(w, fmt.Sprintf("unknown static resource %q", u.Path), http.StatusNotFound) return } // Get the static content if it exists. resource := u.Path[len(StaticResource):] content, ok := staticFiles[resource] if !ok { http.Error(w, fmt.Sprintf("unknown static resource %q", u.Path), http.StatusNotFound) return } // Set Content-Type if we were able to detect it. contentType := mime.TypeByExtension(path.Ext(resource)) if contentType != "" { w.Header().Set("Content-Type", contentType) } if _, err := w.Write(content); err != nil { glog.Errorf("Failed to write response: %v", err) } } cadvisor-0.27.1/storage/000077500000000000000000000000001315410276000150565ustar00rootroot00000000000000cadvisor-0.27.1/storage/bigquery/000077500000000000000000000000001315410276000167055ustar00rootroot00000000000000cadvisor-0.27.1/storage/bigquery/README.md000066400000000000000000000016541315410276000201720ustar00rootroot00000000000000BigQuery Storage Driver ======= [EXPERIMENTAL] Support for BigQuery backend as cAdvisor storage driver. The current implementation takes bunch of BigQuery specific flags for authentication. These will be merged into a single backend config. To run the current version, following flags need to be specified: ``` # Storage driver to use. -storage_driver=bigquery # Information about server-to-server Oauth token. # These can be obtained by creating a Service Account client id under `Google Developer API` # service client id -bq_id="XYZ.apps.googleusercontent.com" # service email address -bq_account="ABC@developer.gserviceaccount.com" # path to pem key (converted from p12 file) -bq_credentials_file="/path/to/key.pem" # project id to use for storing datasets. -bq_project_id="awesome_project" ``` See [Service account Authentication](https://developers.google.com/accounts/docs/OAuth2) for Oauth related details. cadvisor-0.27.1/storage/bigquery/bigquery.go000066400000000000000000000173431315410276000210730ustar00rootroot00000000000000// 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 bigquery import ( "os" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/storage" "github.com/google/cadvisor/storage/bigquery/client" bigquery "google.golang.org/api/bigquery/v2" ) func init() { storage.RegisterStorageDriver("bigquery", new) } type bigqueryStorage struct { client *client.Client machineName string } const ( // Bigquery schema types typeTimestamp string = "TIMESTAMP" typeString string = "STRING" typeInteger string = "INTEGER" colTimestamp string = "timestamp" colMachineName string = "machine" colContainerName string = "container_name" colCpuCumulativeUsage string = "cpu_cumulative_usage" // Cumulative Cpu usage in system and user mode colCpuCumulativeUsageSystem string = "cpu_cumulative_usage_system" colCpuCumulativeUsageUser string = "cpu_cumulative_usage_user" // Memory usage colMemoryUsage string = "memory_usage" // Working set size colMemoryWorkingSet string = "memory_working_set" // Container page fault colMemoryContainerPgfault string = "memory_container_pgfault" // Constainer major page fault colMemoryContainerPgmajfault string = "memory_container_pgmajfault" // Hierarchical page fault colMemoryHierarchicalPgfault string = "memory_hierarchical_pgfault" // Hierarchical major page fault colMemoryHierarchicalPgmajfault string = "memory_hierarchical_pgmajfault" // Cumulative count of bytes received. colRxBytes string = "rx_bytes" // Cumulative count of receive errors encountered. colRxErrors string = "rx_errors" // Cumulative count of bytes transmitted. colTxBytes string = "tx_bytes" // Cumulative count of transmit errors encountered. colTxErrors string = "tx_errors" // Filesystem device. colFsDevice = "fs_device" // Filesystem limit. colFsLimit = "fs_limit" // Filesystem available space. colFsUsage = "fs_usage" ) func new() (storage.StorageDriver, error) { hostname, err := os.Hostname() if err != nil { return nil, err } return newStorage( hostname, *storage.ArgDbTable, *storage.ArgDbName, ) } // TODO(jnagal): Infer schema through reflection. (See bigquery/client/example) func (self *bigqueryStorage) GetSchema() *bigquery.TableSchema { fields := make([]*bigquery.TableFieldSchema, 19) i := 0 fields[i] = &bigquery.TableFieldSchema{ Type: typeTimestamp, Name: colTimestamp, Mode: "REQUIRED", } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeString, Name: colMachineName, Mode: "REQUIRED", } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeString, Name: colContainerName, Mode: "REQUIRED", } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colCpuCumulativeUsage, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colCpuCumulativeUsageSystem, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colCpuCumulativeUsageUser, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colMemoryUsage, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colMemoryWorkingSet, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colMemoryContainerPgfault, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colMemoryContainerPgmajfault, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colMemoryHierarchicalPgfault, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colMemoryHierarchicalPgmajfault, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colRxBytes, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colRxErrors, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colTxBytes, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colTxErrors, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeString, Name: colFsDevice, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colFsLimit, } i++ fields[i] = &bigquery.TableFieldSchema{ Type: typeInteger, Name: colFsUsage, } return &bigquery.TableSchema{ Fields: fields, } } func (self *bigqueryStorage) containerStatsToRows( ref info.ContainerReference, stats *info.ContainerStats, ) (row map[string]interface{}) { row = make(map[string]interface{}) // Timestamp row[colTimestamp] = stats.Timestamp // Machine name row[colMachineName] = self.machineName // Container name name := ref.Name if len(ref.Aliases) > 0 { name = ref.Aliases[0] } row[colContainerName] = name // Cumulative Cpu Usage row[colCpuCumulativeUsage] = stats.Cpu.Usage.Total // Cumulative Cpu Usage in system mode row[colCpuCumulativeUsageSystem] = stats.Cpu.Usage.System // Cumulative Cpu Usage in user mode row[colCpuCumulativeUsageUser] = stats.Cpu.Usage.User // Memory Usage row[colMemoryUsage] = stats.Memory.Usage // Working set size row[colMemoryWorkingSet] = stats.Memory.WorkingSet // container page fault row[colMemoryContainerPgfault] = stats.Memory.ContainerData.Pgfault // container major page fault row[colMemoryContainerPgmajfault] = stats.Memory.ContainerData.Pgmajfault // hierarchical page fault row[colMemoryHierarchicalPgfault] = stats.Memory.HierarchicalData.Pgfault // hierarchical major page fault row[colMemoryHierarchicalPgmajfault] = stats.Memory.HierarchicalData.Pgmajfault // Network stats. row[colRxBytes] = stats.Network.RxBytes row[colRxErrors] = stats.Network.RxErrors row[colTxBytes] = stats.Network.TxBytes row[colTxErrors] = stats.Network.TxErrors // TODO(jnagal): Handle per-cpu stats. return } func (self *bigqueryStorage) containerFilesystemStatsToRows( ref info.ContainerReference, stats *info.ContainerStats, ) (rows []map[string]interface{}) { for _, fsStat := range stats.Filesystem { row := make(map[string]interface{}, 0) row[colFsDevice] = fsStat.Device row[colFsLimit] = fsStat.Limit row[colFsUsage] = fsStat.Usage rows = append(rows, row) } return rows } func (self *bigqueryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { if stats == nil { return nil } rows := make([]map[string]interface{}, 0) rows = append(rows, self.containerStatsToRows(ref, stats)) rows = append(rows, self.containerFilesystemStatsToRows(ref, stats)...) for _, row := range rows { err := self.client.InsertRow(row) if err != nil { return err } } return nil } func (self *bigqueryStorage) Close() error { self.client.Close() self.client = nil return nil } // Create a new bigquery storage driver. // machineName: A unique identifier to identify the host that current cAdvisor // instance is running on. // tableName: BigQuery table used for storing stats. func newStorage(machineName, datasetId, tableName string) (storage.StorageDriver, error) { bqClient, err := client.NewClient() if err != nil { return nil, err } err = bqClient.CreateDataset(datasetId) if err != nil { return nil, err } ret := &bigqueryStorage{ client: bqClient, machineName: machineName, } schema := ret.GetSchema() err = bqClient.CreateTable(tableName, schema) if err != nil { return nil, err } return ret, nil } cadvisor-0.27.1/storage/bigquery/client/000077500000000000000000000000001315410276000201635ustar00rootroot00000000000000cadvisor-0.27.1/storage/bigquery/client/client.go000066400000000000000000000145441315410276000220000ustar00rootroot00000000000000// 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 client import ( "flag" "fmt" "io/ioutil" "strings" "golang.org/x/oauth2" "golang.org/x/oauth2/jwt" bigquery "google.golang.org/api/bigquery/v2" ) var ( // TODO(jnagal): Condense all flags to an identity file and a pem key file. clientId = flag.String("bq_id", "", "Client ID") clientSecret = flag.String("bq_secret", "notasecret", "Client Secret") projectId = flag.String("bq_project_id", "", "Bigquery project ID") serviceAccount = flag.String("bq_account", "", "Service account email") pemFile = flag.String("bq_credentials_file", "", "Credential Key file (pem)") ) const ( errAlreadyExists string = "Error 409: Already Exists" ) type Client struct { service *bigquery.Service token *oauth2.Token datasetId string tableId string } // Helper method to create an authenticated connection. func connect() (*oauth2.Token, *bigquery.Service, error) { if *clientId == "" { return nil, nil, fmt.Errorf("no client id specified") } if *serviceAccount == "" { return nil, nil, fmt.Errorf("no service account specified") } if *projectId == "" { return nil, nil, fmt.Errorf("no project id specified") } authScope := bigquery.BigqueryScope if *pemFile == "" { return nil, nil, fmt.Errorf("no credentials specified") } pemBytes, err := ioutil.ReadFile(*pemFile) if err != nil { return nil, nil, fmt.Errorf("could not access credential file %v - %v", pemFile, err) } jwtConfig := &jwt.Config{ Email: *serviceAccount, Scopes: []string{authScope}, PrivateKey: pemBytes, TokenURL: "https://accounts.google.com/o/oauth2/token", } token, err := jwtConfig.TokenSource(oauth2.NoContext).Token() if err != nil { return nil, nil, err } if !token.Valid() { return nil, nil, fmt.Errorf("invalid token for BigQuery oauth") } config := &oauth2.Config{ ClientID: *clientId, ClientSecret: *clientSecret, Scopes: []string{authScope}, Endpoint: oauth2.Endpoint{ AuthURL: "https://accounts.google.com/o/oauth2/auth", TokenURL: "https://accounts.google.com/o/oauth2/token", }, } client := config.Client(oauth2.NoContext, token) service, err := bigquery.New(client) if err != nil { fmt.Printf("Failed to create new service: %v\n", err) return nil, nil, err } return token, service, nil } // Creates a new client instance with an authenticated connection to bigquery. func NewClient() (*Client, error) { token, service, err := connect() if err != nil { return nil, err } c := &Client{ token: token, service: service, } return c, nil } func (c *Client) Close() error { c.service = nil return nil } // Helper method to return the bigquery service connection. // Expired connection is refreshed. func (c *Client) getService() (*bigquery.Service, error) { if c.token == nil || c.service == nil { return nil, fmt.Errorf("service not initialized") } // Refresh expired token. if !c.token.Valid() { token, service, err := connect() if err != nil { return nil, err } c.token = token c.service = service return service, nil } return c.service, nil } func (c *Client) PrintDatasets() error { datasetList, err := c.service.Datasets.List(*projectId).Do() if err != nil { fmt.Printf("Failed to get list of datasets\n") return err } else { fmt.Printf("Successfully retrieved datasets. Retrieved: %d\n", len(datasetList.Datasets)) } for _, d := range datasetList.Datasets { fmt.Printf("%s %s\n", d.Id, d.FriendlyName) } return nil } func (c *Client) CreateDataset(datasetId string) error { if c.service == nil { return fmt.Errorf("no service created") } _, err := c.service.Datasets.Insert(*projectId, &bigquery.Dataset{ DatasetReference: &bigquery.DatasetReference{ DatasetId: datasetId, ProjectId: *projectId, }, }).Do() // TODO(jnagal): Do a Get() to verify dataset already exists. if err != nil && !strings.Contains(err.Error(), errAlreadyExists) { return err } c.datasetId = datasetId return nil } // Create a table with provided table ID and schema. // Schema is currently not updated if the table already exists. func (c *Client) CreateTable(tableId string, schema *bigquery.TableSchema) error { if c.service == nil || c.datasetId == "" { return fmt.Errorf("no dataset created") } _, err := c.service.Tables.Get(*projectId, c.datasetId, tableId).Do() if err != nil { // Create a new table. _, err := c.service.Tables.Insert(*projectId, c.datasetId, &bigquery.Table{ Schema: schema, TableReference: &bigquery.TableReference{ DatasetId: c.datasetId, ProjectId: *projectId, TableId: tableId, }, }).Do() if err != nil { return err } } // TODO(jnagal): Update schema if it has changed. We can only extend existing schema. c.tableId = tableId return nil } // Add a row to the connected table. func (c *Client) InsertRow(rowData map[string]interface{}) error { service, _ := c.getService() if service == nil || c.datasetId == "" || c.tableId == "" { return fmt.Errorf("table not setup to add rows") } jsonRows := make(map[string]bigquery.JsonValue) for key, value := range rowData { jsonRows[key] = bigquery.JsonValue(value) } rows := []*bigquery.TableDataInsertAllRequestRows{ { Json: jsonRows, }, } // TODO(jnagal): Batch insert requests. insertRequest := &bigquery.TableDataInsertAllRequest{Rows: rows} result, err := service.Tabledata.InsertAll(*projectId, c.datasetId, c.tableId, insertRequest).Do() if err != nil { return fmt.Errorf("error inserting row: %v", err) } if len(result.InsertErrors) > 0 { errstr := fmt.Sprintf("Insertion for %d rows failed\n", len(result.InsertErrors)) for _, errors := range result.InsertErrors { for _, errorproto := range errors.Errors { errstr += fmt.Sprintf("Error inserting row %d: %+v\n", errors.Index, errorproto) } } return fmt.Errorf(errstr) } return nil } cadvisor-0.27.1/storage/bigquery/client/example/000077500000000000000000000000001315410276000216165ustar00rootroot00000000000000cadvisor-0.27.1/storage/bigquery/client/example/example.go000066400000000000000000000042601315410276000236020ustar00rootroot00000000000000// 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" "time" "github.com/SeanDolphin/bqschema" "github.com/google/cadvisor/storage/bigquery/client" ) type container struct { Name string `json:"name"` CpuUsage uint64 `json:"cpuusage,omitempty"` MemoryUsage uint64 `json:"memoryusage,omitempty"` NetworkUsage uint64 `json:"networkusage,omitempty"` Timestamp time.Time `json:"timestamp"` } func main() { flag.Parse() c, err := client.NewClient() if err != nil { fmt.Printf("Failed to connect to bigquery\n") panic(err) } c.PrintDatasets() // Create a new dataset. err = c.CreateDataset("sampledataset") if err != nil { fmt.Printf("Failed to create dataset %v\n", err) panic(err) } // Create a new table containerData := container{ Name: "test_container", CpuUsage: 123456, MemoryUsage: 1024, NetworkUsage: 9046, Timestamp: time.Now(), } schema, err := bqschema.ToSchema(containerData) if err != nil { fmt.Printf("Failed to create schema") panic(err) } err = c.CreateTable("sampletable", schema) if err != nil { fmt.Printf("Failed to create table") panic(err) } // Add Data m := make(map[string]interface{}) t := time.Now() for i := 0; i < 10; i++ { m["Name"] = containerData.Name m["CpuUsage"] = containerData.CpuUsage + uint64(i*100) m["MemoryUsage"] = containerData.MemoryUsage - uint64(i*10) m["NetworkUsage"] = containerData.NetworkUsage + uint64(i*10) m["Timestamp"] = t.Add(time.Duration(i) * time.Second) err = c.InsertRow(m) if err != nil { fmt.Printf("Failed to insert row") panic(err) } } } cadvisor-0.27.1/storage/common_flags.go000066400000000000000000000025641315410276000200600ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package storage import ( "flag" "time" ) var ArgDbUsername = flag.String("storage_driver_user", "root", "database username") var ArgDbPassword = flag.String("storage_driver_password", "root", "database password") var ArgDbHost = flag.String("storage_driver_host", "localhost:8086", "database host:port") var ArgDbName = flag.String("storage_driver_db", "cadvisor", "database name") var ArgDbTable = flag.String("storage_driver_table", "stats", "table name") var ArgDbIsSecure = flag.Bool("storage_driver_secure", false, "use secure connection with database") var ArgDbBufferDuration = flag.Duration("storage_driver_buffer_duration", 60*time.Second, "Writes in the storage driver will be buffered for this duration, and committed to the non memory backends as a single transaction") cadvisor-0.27.1/storage/elasticsearch/000077500000000000000000000000001315410276000176705ustar00rootroot00000000000000cadvisor-0.27.1/storage/elasticsearch/elasticsearch.go000066400000000000000000000107251315410276000230360ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package elasticsearch import ( "flag" "fmt" "os" "sync" "time" info "github.com/google/cadvisor/info/v1" storage "github.com/google/cadvisor/storage" "gopkg.in/olivere/elastic.v2" ) func init() { storage.RegisterStorageDriver("elasticsearch", new) } type elasticStorage struct { client *elastic.Client machineName string indexName string typeName string lock sync.Mutex } type detailSpec struct { Timestamp int64 `json:"timestamp"` MachineName string `json:"machine_name,omitempty"` ContainerName string `json:"container_Name,omitempty"` ContainerStats *info.ContainerStats `json:"container_stats,omitempty"` } var ( argElasticHost = flag.String("storage_driver_es_host", "http://localhost:9200", "ElasticSearch host:port") argIndexName = flag.String("storage_driver_es_index", "cadvisor", "ElasticSearch index name") argTypeName = flag.String("storage_driver_es_type", "stats", "ElasticSearch type name") argEnableSniffer = flag.Bool("storage_driver_es_enable_sniffer", false, "ElasticSearch uses a sniffing process to find all nodes of your cluster by default, automatically") ) func new() (storage.StorageDriver, error) { hostname, err := os.Hostname() if err != nil { return nil, err } return newStorage( hostname, *argIndexName, *argTypeName, *argElasticHost, *argEnableSniffer, ) } func (self *elasticStorage) containerStatsAndDefaultValues( ref info.ContainerReference, stats *info.ContainerStats) *detailSpec { timestamp := stats.Timestamp.UnixNano() / 1E3 var containerName string if len(ref.Aliases) > 0 { containerName = ref.Aliases[0] } else { containerName = ref.Name } detail := &detailSpec{ Timestamp: timestamp, MachineName: self.machineName, ContainerName: containerName, ContainerStats: stats, } return detail } func (self *elasticStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { if stats == nil { return nil } func() { // AddStats will be invoked simultaneously from multiple threads and only one of them will perform a write. self.lock.Lock() defer self.lock.Unlock() // Add some default params based on ContainerStats detail := self.containerStatsAndDefaultValues(ref, stats) // Index a cadvisor (using JSON serialization) _, err := self.client.Index(). Index(self.indexName). Type(self.typeName). BodyJson(detail). Do() if err != nil { // Handle error fmt.Printf("failed to write stats to ElasticSearch - %s", err) return } }() return nil } func (self *elasticStorage) Close() error { self.client = nil return nil } // machineName: A unique identifier to identify the host that current cAdvisor // instance is running on. // ElasticHost: The host which runs ElasticSearch. func newStorage( machineName, indexName, typeName, elasticHost string, enableSniffer bool, ) (storage.StorageDriver, error) { // Obtain a client and connect to the default Elasticsearch installation // on 127.0.0.1:9200. Of course you can configure your client to connect // to other hosts and configure it in various other ways. client, err := elastic.NewClient( elastic.SetHealthcheck(true), elastic.SetSniff(enableSniffer), elastic.SetHealthcheckInterval(30*time.Second), elastic.SetURL(elasticHost), ) if err != nil { // Handle error return nil, fmt.Errorf("failed to create the elasticsearch client - %s", err) } // Ping the Elasticsearch server to get e.g. the version number info, code, err := client.Ping().URL(elasticHost).Do() if err != nil { // Handle error return nil, fmt.Errorf("failed to ping the elasticsearch - %s", err) } fmt.Printf("Elasticsearch returned with code %d and version %s", code, info.Version.Number) ret := &elasticStorage{ client: client, machineName: machineName, indexName: indexName, typeName: typeName, } return ret, nil } cadvisor-0.27.1/storage/influxdb/000077500000000000000000000000001315410276000166715ustar00rootroot00000000000000cadvisor-0.27.1/storage/influxdb/influxdb.go000066400000000000000000000235441315410276000210430ustar00rootroot00000000000000// 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 influxdb import ( "flag" "fmt" "net/url" "os" "sync" "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/storage" "github.com/google/cadvisor/version" influxdb "github.com/influxdb/influxdb/client" ) func init() { storage.RegisterStorageDriver("influxdb", new) } var argDbRetentionPolicy = flag.String("storage_driver_influxdb_retention_policy", "", "retention policy") type influxdbStorage struct { client *influxdb.Client machineName string database string retentionPolicy string bufferDuration time.Duration lastWrite time.Time points []*influxdb.Point lock sync.Mutex readyToFlush func() bool } // Series names const ( // Cumulative CPU usage serCpuUsageTotal string = "cpu_usage_total" serCpuUsageSystem string = "cpu_usage_system" serCpuUsageUser string = "cpu_usage_user" serCpuUsagePerCpu string = "cpu_usage_per_cpu" // Smoothed average of number of runnable threads x 1000. serLoadAverage string = "load_average" // Memory Usage serMemoryUsage string = "memory_usage" // Working set size serMemoryWorkingSet string = "memory_working_set" // Cumulative count of bytes received. serRxBytes string = "rx_bytes" // Cumulative count of receive errors encountered. serRxErrors string = "rx_errors" // Cumulative count of bytes transmitted. serTxBytes string = "tx_bytes" // Cumulative count of transmit errors encountered. serTxErrors string = "tx_errors" // Filesystem device. serFsDevice string = "fs_device" // Filesystem limit. serFsLimit string = "fs_limit" // Filesystem usage. serFsUsage string = "fs_usage" ) func new() (storage.StorageDriver, error) { hostname, err := os.Hostname() if err != nil { return nil, err } return newStorage( hostname, *storage.ArgDbTable, *storage.ArgDbName, *argDbRetentionPolicy, *storage.ArgDbUsername, *storage.ArgDbPassword, *storage.ArgDbHost, *storage.ArgDbIsSecure, *storage.ArgDbBufferDuration, ) } // Field names const ( fieldValue string = "value" fieldType string = "type" fieldDevice string = "device" ) // Tag names const ( tagMachineName string = "machine" tagContainerName string = "container_name" ) func (self *influxdbStorage) containerFilesystemStatsToPoints( ref info.ContainerReference, stats *info.ContainerStats) (points []*influxdb.Point) { if len(stats.Filesystem) == 0 { return points } for _, fsStat := range stats.Filesystem { tagsFsUsage := map[string]string{ fieldDevice: fsStat.Device, fieldType: "usage", } fieldsFsUsage := map[string]interface{}{ fieldValue: int64(fsStat.Usage), } pointFsUsage := &influxdb.Point{ Measurement: serFsUsage, Tags: tagsFsUsage, Fields: fieldsFsUsage, } tagsFsLimit := map[string]string{ fieldDevice: fsStat.Device, fieldType: "limit", } fieldsFsLimit := map[string]interface{}{ fieldValue: int64(fsStat.Limit), } pointFsLimit := &influxdb.Point{ Measurement: serFsLimit, Tags: tagsFsLimit, Fields: fieldsFsLimit, } points = append(points, pointFsUsage, pointFsLimit) } self.tagPoints(ref, stats, points) return points } // Set tags and timestamp for all points of the batch. // Points should inherit the tags that are set for BatchPoints, but that does not seem to work. func (self *influxdbStorage) tagPoints(ref info.ContainerReference, stats *info.ContainerStats, points []*influxdb.Point) { // Use container alias if possible var containerName string if len(ref.Aliases) > 0 { containerName = ref.Aliases[0] } else { containerName = ref.Name } commonTags := map[string]string{ tagMachineName: self.machineName, tagContainerName: containerName, } for i := 0; i < len(points); i++ { // merge with existing tags if any addTagsToPoint(points[i], commonTags) addTagsToPoint(points[i], ref.Labels) points[i].Time = stats.Timestamp } } func (self *influxdbStorage) containerStatsToPoints( ref info.ContainerReference, stats *info.ContainerStats, ) (points []*influxdb.Point) { // CPU usage: Total usage in nanoseconds points = append(points, makePoint(serCpuUsageTotal, stats.Cpu.Usage.Total)) // CPU usage: Time spend in system space (in nanoseconds) points = append(points, makePoint(serCpuUsageSystem, stats.Cpu.Usage.System)) // CPU usage: Time spent in user space (in nanoseconds) points = append(points, makePoint(serCpuUsageUser, stats.Cpu.Usage.User)) // CPU usage per CPU for i := 0; i < len(stats.Cpu.Usage.PerCpu); i++ { point := makePoint(serCpuUsagePerCpu, stats.Cpu.Usage.PerCpu[i]) tags := map[string]string{"instance": fmt.Sprintf("%v", i)} addTagsToPoint(point, tags) points = append(points, point) } // Load Average points = append(points, makePoint(serLoadAverage, stats.Cpu.LoadAverage)) // Memory Usage points = append(points, makePoint(serMemoryUsage, stats.Memory.Usage)) // Working Set Size points = append(points, makePoint(serMemoryWorkingSet, stats.Memory.WorkingSet)) // Network Stats points = append(points, makePoint(serRxBytes, stats.Network.RxBytes)) points = append(points, makePoint(serRxErrors, stats.Network.RxErrors)) points = append(points, makePoint(serTxBytes, stats.Network.TxBytes)) points = append(points, makePoint(serTxErrors, stats.Network.TxErrors)) self.tagPoints(ref, stats, points) return points } func (self *influxdbStorage) OverrideReadyToFlush(readyToFlush func() bool) { self.readyToFlush = readyToFlush } func (self *influxdbStorage) defaultReadyToFlush() bool { return time.Since(self.lastWrite) >= self.bufferDuration } func (self *influxdbStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { if stats == nil { return nil } var pointsToFlush []*influxdb.Point func() { // AddStats will be invoked simultaneously from multiple threads and only one of them will perform a write. self.lock.Lock() defer self.lock.Unlock() self.points = append(self.points, self.containerStatsToPoints(ref, stats)...) self.points = append(self.points, self.containerFilesystemStatsToPoints(ref, stats)...) if self.readyToFlush() { pointsToFlush = self.points self.points = make([]*influxdb.Point, 0) self.lastWrite = time.Now() } }() if len(pointsToFlush) > 0 { points := make([]influxdb.Point, len(pointsToFlush)) for i, p := range pointsToFlush { points[i] = *p } batchTags := map[string]string{tagMachineName: self.machineName} bp := influxdb.BatchPoints{ Points: points, Database: self.database, RetentionPolicy: self.retentionPolicy, Tags: batchTags, Time: stats.Timestamp, } response, err := self.client.Write(bp) if err != nil || checkResponseForErrors(response) != nil { return fmt.Errorf("failed to write stats to influxDb - %s", err) } } return nil } func (self *influxdbStorage) Close() error { self.client = nil return nil } // machineName: A unique identifier to identify the host that current cAdvisor // instance is running on. // influxdbHost: The host which runs influxdb (host:port) func newStorage( machineName, tablename, database, retentionPolicy, username, password, influxdbHost string, isSecure bool, bufferDuration time.Duration, ) (*influxdbStorage, error) { url := &url.URL{ Scheme: "http", Host: influxdbHost, } if isSecure { url.Scheme = "https" } config := &influxdb.Config{ URL: *url, Username: username, Password: password, UserAgent: fmt.Sprintf("%v/%v", "cAdvisor", version.Info["version"]), } client, err := influxdb.NewClient(*config) if err != nil { return nil, err } ret := &influxdbStorage{ client: client, machineName: machineName, database: database, retentionPolicy: retentionPolicy, bufferDuration: bufferDuration, lastWrite: time.Now(), points: make([]*influxdb.Point, 0), } ret.readyToFlush = ret.defaultReadyToFlush return ret, nil } // Creates a measurement point with a single value field func makePoint(name string, value interface{}) *influxdb.Point { fields := map[string]interface{}{ fieldValue: toSignedIfUnsigned(value), } return &influxdb.Point{ Measurement: name, Fields: fields, } } // Adds additional tags to the existing tags of a point func addTagsToPoint(point *influxdb.Point, tags map[string]string) { if point.Tags == nil { point.Tags = tags } else { for k, v := range tags { point.Tags[k] = v } } } // Checks response for possible errors func checkResponseForErrors(response *influxdb.Response) error { const msg = "failed to write stats to influxDb - %s" if response != nil && response.Err != nil { return fmt.Errorf(msg, response.Err) } if response != nil && response.Results != nil { for _, result := range response.Results { if result.Err != nil { return fmt.Errorf(msg, result.Err) } if result.Series != nil { for _, row := range result.Series { if row.Err != nil { return fmt.Errorf(msg, row.Err) } } } } } return nil } // Some stats have type unsigned integer, but the InfluxDB client accepts only signed integers. func toSignedIfUnsigned(value interface{}) interface{} { switch v := value.(type) { case uint64: return int64(v) case uint32: return int32(v) case uint16: return int16(v) case uint8: return int8(v) case uint: return int(v) } return value } cadvisor-0.27.1/storage/influxdb/influxdb_test.go000066400000000000000000000202451315410276000220750ustar00rootroot00000000000000// 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. // +build influxdb_test // To run unit test: go test -tags influxdb_test package influxdb import ( "fmt" "math/rand" "net/url" "reflect" "testing" "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/storage" "github.com/google/cadvisor/storage/test" influxdb "github.com/influxdb/influxdb/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // The duration in seconds for which stats will be buffered in the influxdb driver. const kCacheDuration = 1 type influxDbTestStorageDriver struct { count int buffer int base storage.StorageDriver } func (self *influxDbTestStorageDriver) readyToFlush() bool { return self.count >= self.buffer } func (self *influxDbTestStorageDriver) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { self.count++ return self.base.AddStats(ref, stats) } func (self *influxDbTestStorageDriver) Close() error { return self.base.Close() } func (self *influxDbTestStorageDriver) StatsEq(a, b *info.ContainerStats) bool { if !test.TimeEq(a.Timestamp, b.Timestamp, 10*time.Millisecond) { return false } // Check only the stats populated in influxdb. if !reflect.DeepEqual(a.Cpu.Usage, b.Cpu.Usage) { return false } if a.Memory.Usage != b.Memory.Usage { return false } if a.Memory.WorkingSet != b.Memory.WorkingSet { return false } if !reflect.DeepEqual(a.Network, b.Network) { return false } if !reflect.DeepEqual(a.Filesystem, b.Filesystem) { return false } return true } func runStorageTest(f func(test.TestStorageDriver, *testing.T), t *testing.T, bufferCount int) { machineName := "machineA" table := "cadvisor_table" database := "cadvisor_test" username := "root" password := "root" hostname := "localhost:8086" retentionPolicy := "cadvisor_test_rp" // percentilesDuration := 10 * time.Minute config := influxdb.Config{ URL: url.URL{Scheme: "http", Host: hostname}, Username: username, Password: password, } client, err := influxdb.NewClient(config) if err != nil { t.Fatal(err) } // Re-create the database first. if err := prepareDatabase(client, database, retentionPolicy); err != nil { t.Fatal(err) } // Delete all data by the end of the call. // defer client.Query(influxdb.Query{Command: fmt.Sprintf("drop database \"%v\"", database)}) driver, err := newStorage(machineName, table, database, retentionPolicy, username, password, hostname, false, time.Duration(bufferCount)) if err != nil { t.Fatal(err) } defer driver.Close() testDriver := &influxDbTestStorageDriver{buffer: bufferCount} driver.OverrideReadyToFlush(testDriver.readyToFlush) testDriver.base = driver // Generate another container's data on same machine. test.StorageDriverFillRandomStatsFunc("containerOnSameMachine", 100, testDriver, t) // Generate another container's data on another machine. driverForAnotherMachine, err := newStorage("machineB", table, database, retentionPolicy, username, password, hostname, false, time.Duration(bufferCount)) if err != nil { t.Fatal(err) } defer driverForAnotherMachine.Close() testDriverOtherMachine := &influxDbTestStorageDriver{buffer: bufferCount} driverForAnotherMachine.OverrideReadyToFlush(testDriverOtherMachine.readyToFlush) testDriverOtherMachine.base = driverForAnotherMachine test.StorageDriverFillRandomStatsFunc("containerOnAnotherMachine", 100, testDriverOtherMachine, t) f(testDriver, t) } func prepareDatabase(client *influxdb.Client, database string, retentionPolicy string) error { dropDbQuery := influxdb.Query{ Command: fmt.Sprintf("drop database \"%v\"", database), } createDbQuery := influxdb.Query{ Command: fmt.Sprintf("create database \"%v\"", database), } // A default retention policy must always be present. // Depending on the InfluxDB configuration it may be created automatically with the database or not. // TODO create ret. policy only if not present createPolicyQuery := influxdb.Query{ Command: fmt.Sprintf("create retention policy \"%v\" on \"%v\" duration 1h replication 1 default", retentionPolicy, database), } _, err := client.Query(dropDbQuery) if err != nil { return err } _, err = client.Query(createDbQuery) if err != nil { return err } _, err = client.Query(createPolicyQuery) return err } func TestContainerFileSystemStatsToPoints(t *testing.T) { assert := assert.New(t) machineName := "testMachine" table := "cadvisor_table" database := "cadvisor_test" retentionPolicy := "cadvisor_test_rp" username := "root" password := "root" influxdbHost := "localhost:8086" storage, err := newStorage(machineName, table, database, retentionPolicy, username, password, influxdbHost, false, 2*time.Minute) assert.Nil(err) ref := info.ContainerReference{ Name: "containerName", } stats := &info.ContainerStats{} points := storage.containerFilesystemStatsToPoints(ref, stats) // stats.Filesystem is always nil, not sure why assert.Nil(points) } func TestContainerStatsToPoints(t *testing.T) { // Given storage, err := createTestStorage() require.Nil(t, err) require.NotNil(t, storage) ref, stats := createTestStats() require.Nil(t, err) require.NotNil(t, stats) // When points := storage.containerStatsToPoints(*ref, stats) // Then assert.NotEmpty(t, points) assert.Len(t, points, 10+len(stats.Cpu.Usage.PerCpu)) assertContainsPointWithValue(t, points, serCpuUsageTotal, stats.Cpu.Usage.Total) assertContainsPointWithValue(t, points, serCpuUsageSystem, stats.Cpu.Usage.System) assertContainsPointWithValue(t, points, serCpuUsageUser, stats.Cpu.Usage.User) assertContainsPointWithValue(t, points, serMemoryUsage, stats.Memory.Usage) assertContainsPointWithValue(t, points, serLoadAverage, stats.Cpu.LoadAverage) assertContainsPointWithValue(t, points, serMemoryWorkingSet, stats.Memory.WorkingSet) assertContainsPointWithValue(t, points, serRxBytes, stats.Network.RxBytes) assertContainsPointWithValue(t, points, serRxErrors, stats.Network.RxErrors) assertContainsPointWithValue(t, points, serTxBytes, stats.Network.TxBytes) assertContainsPointWithValue(t, points, serTxBytes, stats.Network.TxErrors) for _, cpu_usage := range stats.Cpu.Usage.PerCpu { assertContainsPointWithValue(t, points, serCpuUsagePerCpu, cpu_usage) } } func assertContainsPointWithValue(t *testing.T, points []*influxdb.Point, name string, value interface{}) bool { found := false for _, point := range points { if point.Measurement == name && point.Fields[fieldValue] == toSignedIfUnsigned(value) { found = true break } } return assert.True(t, found, "no point found with name='%v' and value=%v", name, value) } func createTestStorage() (*influxdbStorage, error) { machineName := "testMachine" table := "cadvisor_table" database := "cadvisor_test" retentionPolicy := "cadvisor_test_rp" username := "root" password := "root" influxdbHost := "localhost:8086" storage, err := newStorage(machineName, table, database, retentionPolicy, username, password, influxdbHost, false, 2*time.Minute) return storage, err } func createTestStats() (*info.ContainerReference, *info.ContainerStats) { ref := &info.ContainerReference{ Name: "testContainername", Aliases: []string{"testContainerAlias1", "testContainerAlias2"}, } cpuUsage := info.CpuUsage{ Total: uint64(rand.Intn(10000)), PerCpu: []uint64{uint64(rand.Intn(1000)), uint64(rand.Intn(1000)), uint64(rand.Intn(1000))}, User: uint64(rand.Intn(10000)), System: uint64(rand.Intn(10000)), } stats := &info.ContainerStats{ Timestamp: time.Now(), Cpu: info.CpuStats{ Usage: cpuUsage, LoadAverage: int32(rand.Intn(1000)), }, } return ref, stats } cadvisor-0.27.1/storage/kafka/000077500000000000000000000000001315410276000161335ustar00rootroot00000000000000cadvisor-0.27.1/storage/kafka/kafka.go000066400000000000000000000104171315410276000175420ustar00rootroot00000000000000// Copyright 2016 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 kafka import ( "crypto/tls" "crypto/x509" "encoding/json" "flag" "io/ioutil" "os" "strings" "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/storage" "github.com/google/cadvisor/utils/container" kafka "github.com/Shopify/sarama" "github.com/golang/glog" ) func init() { storage.RegisterStorageDriver("kafka", new) } var ( brokers = flag.String("storage_driver_kafka_broker_list", "localhost:9092", "kafka broker(s) csv") topic = flag.String("storage_driver_kafka_topic", "stats", "kafka topic") certFile = flag.String("storage_driver_kafka_ssl_cert", "", "optional certificate file for TLS client authentication") keyFile = flag.String("storage_driver_kafka_ssl_key", "", "optional key file for TLS client authentication") caFile = flag.String("storage_driver_kafka_ssl_ca", "", "optional certificate authority file for TLS client authentication") verifySSL = flag.Bool("storage_driver_kafka_ssl_verify", true, "verify ssl certificate chain") ) type kafkaStorage struct { producer kafka.AsyncProducer topic string machineName string } type detailSpec struct { Timestamp time.Time `json:"timestamp"` MachineName string `json:"machine_name,omitempty"` ContainerName string `json:"container_Name,omitempty"` ContainerID string `json:"container_Id,omitempty"` ContainerLabels map[string]string `json:"container_labels,omitempty"` ContainerStats *info.ContainerStats `json:"container_stats,omitempty"` } func (driver *kafkaStorage) infoToDetailSpec(ref info.ContainerReference, stats *info.ContainerStats) *detailSpec { timestamp := time.Now() containerID := ref.Id containerLabels := ref.Labels containerName := container.GetPreferredName(ref) detail := &detailSpec{ Timestamp: timestamp, MachineName: driver.machineName, ContainerName: containerName, ContainerID: containerID, ContainerLabels: containerLabels, ContainerStats: stats, } return detail } func (driver *kafkaStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { detail := driver.infoToDetailSpec(ref, stats) b, err := json.Marshal(detail) driver.producer.Input() <- &kafka.ProducerMessage{ Topic: driver.topic, Value: kafka.StringEncoder(b), } return err } func (self *kafkaStorage) Close() error { return self.producer.Close() } func new() (storage.StorageDriver, error) { machineName, err := os.Hostname() if err != nil { return nil, err } return newStorage(machineName) } func generateTLSConfig() (*tls.Config, error) { if *certFile != "" && *keyFile != "" && *caFile != "" { cert, err := tls.LoadX509KeyPair(*certFile, *keyFile) if err != nil { return nil, err } caCert, err := ioutil.ReadFile(*caFile) if err != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) return &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caCertPool, InsecureSkipVerify: *verifySSL, }, nil } return nil, nil } func newStorage(machineName string) (storage.StorageDriver, error) { config := kafka.NewConfig() tlsConfig, err := generateTLSConfig() if err != nil { return nil, err } if tlsConfig != nil { config.Net.TLS.Enable = true config.Net.TLS.Config = tlsConfig } config.Producer.RequiredAcks = kafka.WaitForAll brokerList := strings.Split(*brokers, ",") glog.V(4).Infof("Kafka brokers:%q", brokers) producer, err := kafka.NewAsyncProducer(brokerList, config) if err != nil { return nil, err } ret := &kafkaStorage{ producer: producer, topic: *topic, machineName: machineName, } return ret, nil } cadvisor-0.27.1/storage/redis/000077500000000000000000000000001315410276000161645ustar00rootroot00000000000000cadvisor-0.27.1/storage/redis/redis.go000066400000000000000000000074041315410276000176260ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package redis import ( "encoding/json" "os" "sync" "time" info "github.com/google/cadvisor/info/v1" storage "github.com/google/cadvisor/storage" redis "github.com/garyburd/redigo/redis" ) func init() { storage.RegisterStorageDriver("redis", new) } type redisStorage struct { conn redis.Conn machineName string redisKey string bufferDuration time.Duration lastWrite time.Time lock sync.Mutex readyToFlush func() bool } type detailSpec struct { Timestamp int64 `json:"timestamp"` MachineName string `json:"machine_name,omitempty"` ContainerName string `json:"container_Name,omitempty"` ContainerStats *info.ContainerStats `json:"container_stats,omitempty"` } func new() (storage.StorageDriver, error) { hostname, err := os.Hostname() if err != nil { return nil, err } return newStorage( hostname, *storage.ArgDbName, *storage.ArgDbHost, *storage.ArgDbBufferDuration, ) } func (self *redisStorage) defaultReadyToFlush() bool { return time.Since(self.lastWrite) >= self.bufferDuration } // We must add some default params (for example: MachineName,ContainerName...)because containerStats do not include them func (self *redisStorage) containerStatsAndDefaultValues(ref info.ContainerReference, stats *info.ContainerStats) *detailSpec { timestamp := stats.Timestamp.UnixNano() / 1E3 var containerName string if len(ref.Aliases) > 0 { containerName = ref.Aliases[0] } else { containerName = ref.Name } detail := &detailSpec{ Timestamp: timestamp, MachineName: self.machineName, ContainerName: containerName, ContainerStats: stats, } return detail } // Push the data into redis func (self *redisStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { if stats == nil { return nil } var seriesToFlush []byte func() { // AddStats will be invoked simultaneously from multiple threads and only one of them will perform a write. self.lock.Lock() defer self.lock.Unlock() // Add some default params based on containerStats detail := self.containerStatsAndDefaultValues(ref, stats) // To json b, _ := json.Marshal(detail) if self.readyToFlush() { seriesToFlush = b self.lastWrite = time.Now() } }() if len(seriesToFlush) > 0 { // We use redis's "LPUSH" to push the data to the redis self.conn.Send("LPUSH", self.redisKey, seriesToFlush) } return nil } func (self *redisStorage) Close() error { return self.conn.Close() } // Create a new redis storage driver. // machineName: A unique identifier to identify the host that runs the current cAdvisor // instance is running on. // redisHost: The host which runs redis. // redisKey: The key for the Data that stored in the redis func newStorage( machineName, redisKey, redisHost string, bufferDuration time.Duration, ) (storage.StorageDriver, error) { conn, err := redis.Dial("tcp", redisHost) if err != nil { return nil, err } ret := &redisStorage{ conn: conn, machineName: machineName, redisKey: redisKey, bufferDuration: bufferDuration, lastWrite: time.Now(), } ret.readyToFlush = ret.defaultReadyToFlush return ret, nil } cadvisor-0.27.1/storage/statsd/000077500000000000000000000000001315410276000163605ustar00rootroot00000000000000cadvisor-0.27.1/storage/statsd/client/000077500000000000000000000000001315410276000176365ustar00rootroot00000000000000cadvisor-0.27.1/storage/statsd/client/client.go000066400000000000000000000031311315410276000214410ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package client import ( "fmt" "net" "github.com/golang/glog" ) type Client struct { HostPort string Namespace string conn net.Conn } func (self *Client) Open() error { conn, err := net.Dial("udp", self.HostPort) if err != nil { glog.Errorf("failed to open udp connection to %q: %v", self.HostPort, err) return err } self.conn = conn return nil } func (self *Client) Close() error { self.conn.Close() self.conn = nil return nil } // Simple send to statsd daemon without sampling. func (self *Client) Send(namespace, containerName, key string, value uint64) error { // only send counter value formatted := fmt.Sprintf("%s.%s.%s:%d|g", namespace, containerName, key, value) _, err := fmt.Fprintf(self.conn, formatted) if err != nil { return fmt.Errorf("failed to send data %q: %v", formatted, err) } return nil } func New(hostPort string) (*Client, error) { Client := Client{HostPort: hostPort} if err := Client.Open(); err != nil { return nil, err } return &Client, nil } cadvisor-0.27.1/storage/statsd/statsd.go000066400000000000000000000076031315410276000202170ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package statsd import ( info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/storage" client "github.com/google/cadvisor/storage/statsd/client" ) func init() { storage.RegisterStorageDriver("statsd", new) } type statsdStorage struct { client *client.Client Namespace string } const ( colCpuCumulativeUsage string = "cpu_cumulative_usage" // CPU system colCpuUsageSystem string = "cpu_usage_system" // CPU user colCpuUsageUser string = "cpu_usage_user" // CPU average load colCpuLoadAverage string = "cpu_load_average" // Memory Usage colMemoryUsage string = "memory_usage" // Working set size colMemoryWorkingSet string = "memory_working_set" // Cumulative count of bytes received. colRxBytes string = "rx_bytes" // Cumulative count of receive errors encountered. colRxErrors string = "rx_errors" // Cumulative count of bytes transmitted. colTxBytes string = "tx_bytes" // Cumulative count of transmit errors encountered. colTxErrors string = "tx_errors" // Filesystem summary colFsSummary = "fs_summary" // Filesystem limit. colFsLimit = "fs_limit" // Filesystem usage. colFsUsage = "fs_usage" ) func new() (storage.StorageDriver, error) { return newStorage(*storage.ArgDbName, *storage.ArgDbHost) } func (self *statsdStorage) containerStatsToValues( stats *info.ContainerStats, ) (series map[string]uint64) { series = make(map[string]uint64) // Cumulative Cpu Usage series[colCpuCumulativeUsage] = stats.Cpu.Usage.Total // Cpu usage series[colCpuUsageSystem] = stats.Cpu.Usage.System series[colCpuUsageUser] = stats.Cpu.Usage.User series[colCpuLoadAverage] = uint64(stats.Cpu.LoadAverage) // Memory Usage series[colMemoryUsage] = stats.Memory.Usage // Working set size series[colMemoryWorkingSet] = stats.Memory.WorkingSet // Network stats. series[colRxBytes] = stats.Network.RxBytes series[colRxErrors] = stats.Network.RxErrors series[colTxBytes] = stats.Network.TxBytes series[colTxErrors] = stats.Network.TxErrors return series } func (self *statsdStorage) containerFsStatsToValues( series *map[string]uint64, stats *info.ContainerStats, ) { for _, fsStat := range stats.Filesystem { // Summary stats. (*series)[colFsSummary+"."+colFsLimit] += fsStat.Limit (*series)[colFsSummary+"."+colFsUsage] += fsStat.Usage // Per device stats. (*series)[fsStat.Device+"."+colFsLimit] = fsStat.Limit (*series)[fsStat.Device+"."+colFsUsage] = fsStat.Usage } } // Push the data into redis func (self *statsdStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { if stats == nil { return nil } var containerName string if len(ref.Aliases) > 0 { containerName = ref.Aliases[0] } else { containerName = ref.Name } series := self.containerStatsToValues(stats) self.containerFsStatsToValues(&series, stats) for key, value := range series { err := self.client.Send(self.Namespace, containerName, key, value) if err != nil { return err } } return nil } func (self *statsdStorage) Close() error { self.client.Close() self.client = nil return nil } func newStorage(namespace, hostPort string) (*statsdStorage, error) { statsdClient, err := client.New(hostPort) if err != nil { return nil, err } statsdStorage := &statsdStorage{ client: statsdClient, Namespace: namespace, } return statsdStorage, nil } cadvisor-0.27.1/storage/stdout/000077500000000000000000000000001315410276000164005ustar00rootroot00000000000000cadvisor-0.27.1/storage/stdout/stdout.go000066400000000000000000000064401315410276000202550ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package stdout import ( "bytes" "fmt" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/storage" ) func init() { storage.RegisterStorageDriver("stdout", new) } type stdoutStorage struct { Namespace string } const ( colCpuCumulativeUsage = "cpu_cumulative_usage" // Memory Usage colMemoryUsage = "memory_usage" // Working set size colMemoryWorkingSet = "memory_working_set" // Cumulative count of bytes received. colRxBytes = "rx_bytes" // Cumulative count of receive errors encountered. colRxErrors = "rx_errors" // Cumulative count of bytes transmitted. colTxBytes = "tx_bytes" // Cumulative count of transmit errors encountered. colTxErrors = "tx_errors" // Filesystem summary colFsSummary = "fs_summary" // Filesystem limit. colFsLimit = "fs_limit" // Filesystem usage. colFsUsage = "fs_usage" ) func new() (storage.StorageDriver, error) { return newStorage(*storage.ArgDbHost) } func (driver *stdoutStorage) containerStatsToValues(stats *info.ContainerStats) (series map[string]uint64) { series = make(map[string]uint64) // Cumulative Cpu Usage series[colCpuCumulativeUsage] = stats.Cpu.Usage.Total // Memory Usage series[colMemoryUsage] = stats.Memory.Usage // Working set size series[colMemoryWorkingSet] = stats.Memory.WorkingSet // Network stats. series[colRxBytes] = stats.Network.RxBytes series[colRxErrors] = stats.Network.RxErrors series[colTxBytes] = stats.Network.TxBytes series[colTxErrors] = stats.Network.TxErrors return series } func (driver *stdoutStorage) containerFsStatsToValues(series *map[string]uint64, stats *info.ContainerStats) { for _, fsStat := range stats.Filesystem { // Summary stats. (*series)[colFsSummary+"."+colFsLimit] += fsStat.Limit (*series)[colFsSummary+"."+colFsUsage] += fsStat.Usage // Per device stats. (*series)[fsStat.Device+"."+colFsLimit] = fsStat.Limit (*series)[fsStat.Device+"."+colFsUsage] = fsStat.Usage } } func (driver *stdoutStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { if stats == nil { return nil } containerName := ref.Name if len(ref.Aliases) > 0 { containerName = ref.Aliases[0] } var buffer bytes.Buffer buffer.WriteString(fmt.Sprintf("cName=%s host=%s", containerName, driver.Namespace)) series := driver.containerStatsToValues(stats) driver.containerFsStatsToValues(&series, stats) for key, value := range series { buffer.WriteString(fmt.Sprintf(" %s=%v", key, value)) } _, err := fmt.Println(buffer.String()) return err } func (driver *stdoutStorage) Close() error { return nil } func newStorage(namespace string) (*stdoutStorage, error) { stdoutStorage := &stdoutStorage{ Namespace: namespace, } return stdoutStorage, nil } cadvisor-0.27.1/storage/storage.go000066400000000000000000000031421315410276000170510ustar00rootroot00000000000000// 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 ( "fmt" "sort" info "github.com/google/cadvisor/info/v1" ) type StorageDriver interface { AddStats(ref info.ContainerReference, stats *info.ContainerStats) 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 } type StorageDriverFunc func() (StorageDriver, error) var registeredPlugins = map[string](StorageDriverFunc){} func RegisterStorageDriver(name string, f StorageDriverFunc) { registeredPlugins[name] = f } func New(name string) (StorageDriver, error) { if name == "" { return nil, nil } f, ok := registeredPlugins[name] if !ok { return nil, fmt.Errorf("unknown backend storage driver: %s", name) } return f() } func ListDrivers() []string { drivers := make([]string, 0, len(registeredPlugins)) for name := range registeredPlugins { drivers = append(drivers, name) } sort.Strings(drivers) return drivers } cadvisor-0.27.1/storage/test/000077500000000000000000000000001315410276000160355ustar00rootroot00000000000000cadvisor-0.27.1/storage/test/mock.go000066400000000000000000000020751315410276000173210ustar00rootroot00000000000000// 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 ( info "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/mock" ) type MockStorageDriver struct { mock.Mock MockCloseMethod bool } func (self *MockStorageDriver) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { args := self.Called(ref, stats) return args.Error(0) } func (self *MockStorageDriver) Close() error { if self.MockCloseMethod { args := self.Called() return args.Error(0) } return nil } cadvisor-0.27.1/storage/test/storagetests.go000066400000000000000000000066301315410276000211200ustar00rootroot00000000000000// 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" "reflect" "testing" "time" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/storage" ) type TestStorageDriver interface { StatsEq(a *info.ContainerStats, b *info.ContainerStats) bool storage.StorageDriver } 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.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.Cpu.Usage.PerCpu = []uint64{cpuTotalUsage} stats.Memory.Usage = mem[i] stats.Network.RxBytes = uint64(rand.Intn(10000)) stats.Network.RxErrors = uint64(rand.Intn(1000)) stats.Network.TxBytes = uint64(rand.Intn(100000)) stats.Network.TxErrors = uint64(rand.Intn(1000)) stats.Filesystem = make([]info.FsStats, 1) stats.Filesystem[0].Device = "/dev/sda1" stats.Filesystem[0].Limit = 1024000000 stats.Filesystem[0].Usage = 1024000 ret[i] = stats } return ret } func TimeEq(t1, t2 time.Time, tolerance time.Duration) bool { // t1 should not be later than t2 if t1.After(t2) { t1, t2 = t2, t1 } diff := t2.Sub(t1) if diff <= tolerance { return true } return false } const ( // 10ms, i.e. 0.01s timePrecision time.Duration = 10 * time.Millisecond ) // This function is useful because we do not require precise time // representation. func DefaultStatsEq(a, b *info.ContainerStats) bool { if !TimeEq(a.Timestamp, b.Timestamp, timePrecision) { return false } if !reflect.DeepEqual(a.Cpu, b.Cpu) { return false } if !reflect.DeepEqual(a.Memory, b.Memory) { return false } if !reflect.DeepEqual(a.Network, b.Network) { return false } if !reflect.DeepEqual(a.Filesystem, b.Filesystem) { return false } return true } // This function will generate random stats and write // them into the storage. The function will not close the driver func StorageDriverFillRandomStatsFunc( containerName string, N int, driver TestStorageDriver, t *testing.T, ) { 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: containerName, } trace := buildTrace(cpuTrace, memTrace, samplePeriod) for _, stats := range trace { err := driver.AddStats(ref, stats) if err != nil { t.Fatalf("unable to add stats: %v", err) } } } cadvisor-0.27.1/storagedriver.go000066400000000000000000000036651315410276000166330ustar00rootroot00000000000000// 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" "strings" "time" "github.com/google/cadvisor/cache/memory" "github.com/google/cadvisor/storage" _ "github.com/google/cadvisor/storage/bigquery" _ "github.com/google/cadvisor/storage/elasticsearch" _ "github.com/google/cadvisor/storage/influxdb" _ "github.com/google/cadvisor/storage/kafka" _ "github.com/google/cadvisor/storage/redis" _ "github.com/google/cadvisor/storage/statsd" _ "github.com/google/cadvisor/storage/stdout" "github.com/golang/glog" ) var ( storageDriver = flag.String("storage_driver", "", fmt.Sprintf("Storage `driver` to use. Data is always cached shortly in memory, this controls where data is pushed besides the local cache. Empty means none. Options are: , %s", strings.Join(storage.ListDrivers(), ", "))) storageDuration = flag.Duration("storage_duration", 2*time.Minute, "How long to keep data stored (Default: 2min).") ) // NewMemoryStorage creates a memory storage with an optional backend storage option. func NewMemoryStorage() (*memory.InMemoryCache, error) { backendStorage, err := storage.New(*storageDriver) if err != nil { return nil, err } if *storageDriver != "" { glog.Infof("Using backend storage type %q", *storageDriver) } glog.Infof("Caching stats in memory for %v", *storageDuration) return memory.New(*storageDuration, backendStorage), nil } cadvisor-0.27.1/summary/000077500000000000000000000000001315410276000151075ustar00rootroot00000000000000cadvisor-0.27.1/summary/buffer.go000066400000000000000000000036671315410276000167230ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package summary import ( info "github.com/google/cadvisor/info/v2" ) // Manages a buffer of usage samples. // This is similar to stats buffer in cache/memory. // The main difference is that we do not pre-allocate the buffer as most containers // won't live that long. type SamplesBuffer struct { // list of collected samples. samples []info.Usage // maximum size this buffer can grow to. maxSize int // index for the latest sample. index int } // Initializes an empty buffer. func NewSamplesBuffer(size int) *SamplesBuffer { return &SamplesBuffer{ index: -1, maxSize: size, } } // Returns the current number of samples in the buffer. func (s *SamplesBuffer) Size() int { return len(s.samples) } // Add an element to the buffer. Oldest one is overwritten if required. func (s *SamplesBuffer) Add(stat info.Usage) { if len(s.samples) < s.maxSize { s.samples = append(s.samples, stat) s.index++ return } s.index = (s.index + 1) % s.maxSize s.samples[s.index] = stat } // Returns pointers to the last 'n' stats. func (s *SamplesBuffer) RecentStats(n int) []*info.Usage { if n > len(s.samples) { n = len(s.samples) } start := s.index - (n - 1) if start < 0 { start += len(s.samples) } out := make([]*info.Usage, n) for i := 0; i < n; i++ { index := (start + i) % len(s.samples) out[i] = &s.samples[index] } return out } cadvisor-0.27.1/summary/buffer_test.go000066400000000000000000000054161315410276000177540ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package summary import ( "reflect" "testing" info "github.com/google/cadvisor/info/v2" ) func createSample(i uint64) info.Usage { usage := info.Usage{} usage.PercentComplete = 100 usage.Cpu = info.Percentiles{ Present: true, Mean: i * 50, Max: i * 100, Ninety: i * 90, } usage.Memory = info.Percentiles{ Present: true, Mean: i * 50 * 1024, Max: i * 100 * 1024, Ninety: i * 90 * 1024, } return usage } func expectSize(t *testing.T, b *SamplesBuffer, expectedSize int) { if b.Size() != expectedSize { t.Errorf("Expected size %d, got %d", expectedSize, b.Size()) } } func expectElements(t *testing.T, b *SamplesBuffer, expected []info.Usage) { out := b.RecentStats(b.Size()) if len(out) != len(expected) { t.Errorf("Expected %d elements, got %d", len(expected), len(out)) } for i, el := range out { if !reflect.DeepEqual(*el, expected[i]) { t.Errorf("Expected elements %v, got %v", expected[i], *el) } } } func TestEmpty(t *testing.T) { b := NewSamplesBuffer(5) expectSize(t, b, 0) expectElements(t, b, []info.Usage{}) } func TestAddSingleSample(t *testing.T) { b := NewSamplesBuffer(5) sample := createSample(1) b.Add(sample) expectSize(t, b, 1) expectElements(t, b, []info.Usage{sample}) } func TestFullBuffer(t *testing.T) { maxSize := 5 b := NewSamplesBuffer(maxSize) samples := []info.Usage{} for i := 0; i < maxSize; i++ { sample := createSample(uint64(i)) samples = append(samples, sample) b.Add(sample) } expectSize(t, b, maxSize) expectElements(t, b, samples) } func TestOverflow(t *testing.T) { maxSize := 5 overflow := 2 b := NewSamplesBuffer(maxSize) samples := []info.Usage{} for i := 0; i < maxSize+overflow; i++ { sample := createSample(uint64(i)) if i >= overflow { samples = append(samples, sample) } b.Add(sample) } expectSize(t, b, maxSize) expectElements(t, b, samples) } func TestReplaceAll(t *testing.T) { maxSize := 5 b := NewSamplesBuffer(maxSize) samples := []info.Usage{} for i := 0; i < maxSize*2; i++ { sample := createSample(uint64(i)) if i >= maxSize { samples = append(samples, sample) } b.Add(sample) } expectSize(t, b, maxSize) expectElements(t, b, samples) } cadvisor-0.27.1/summary/percentiles.go000066400000000000000000000123611315410276000177560ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Utility methods to calculate percentiles. package summary import ( "fmt" "math" "sort" info "github.com/google/cadvisor/info/v2" ) const secondsToMilliSeconds = 1000 const milliSecondsToNanoSeconds = 1000000 const secondsToNanoSeconds = secondsToMilliSeconds * milliSecondsToNanoSeconds type Uint64Slice []uint64 func (a Uint64Slice) Len() int { return len(a) } func (a Uint64Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a Uint64Slice) Less(i, j int) bool { return a[i] < a[j] } // Get percentile of the provided samples. Round to integer. func (self Uint64Slice) GetPercentile(d float64) uint64 { if d < 0.0 || d > 1.0 { return 0 } count := self.Len() if count == 0 { return 0 } sort.Sort(self) n := float64(d * (float64(count) + 1)) idx, frac := math.Modf(n) index := int(idx) percentile := float64(self[index-1]) if index > 1 && index < count { percentile += frac * float64(self[index]-self[index-1]) } return uint64(percentile) } type mean struct { // current count. count uint64 // current mean. Mean float64 } func (self *mean) Add(value uint64) { self.count++ if self.count == 1 { self.Mean = float64(value) return } c := float64(self.count) v := float64(value) self.Mean = (self.Mean*(c-1) + v) / c } type resource struct { // list of samples being tracked. samples Uint64Slice // average from existing samples. mean mean // maximum value seen so far in the added samples. max uint64 } // Adds a new percentile sample. func (self *resource) Add(p info.Percentiles) { if !p.Present { return } if p.Max > self.max { self.max = p.Max } self.mean.Add(p.Mean) // Selecting 90p of 90p :( self.samples = append(self.samples, p.Ninety) } // Add a single sample. Internally, we convert it to a fake percentile sample. func (self *resource) AddSample(val uint64) { sample := info.Percentiles{ Present: true, Mean: val, Max: val, Fifty: val, Ninety: val, NinetyFive: val, } self.Add(sample) } // Get max, average, and 90p from existing samples. func (self *resource) GetAllPercentiles() info.Percentiles { p := info.Percentiles{} p.Mean = uint64(self.mean.Mean) p.Max = self.max p.Fifty = self.samples.GetPercentile(0.5) p.Ninety = self.samples.GetPercentile(0.9) p.NinetyFive = self.samples.GetPercentile(0.95) p.Present = true return p } func NewResource(size int) *resource { return &resource{ samples: make(Uint64Slice, 0, size), mean: mean{count: 0, Mean: 0}, } } // Return aggregated percentiles from the provided percentile samples. func GetDerivedPercentiles(stats []*info.Usage) info.Usage { cpu := NewResource(len(stats)) memory := NewResource(len(stats)) for _, stat := range stats { cpu.Add(stat.Cpu) memory.Add(stat.Memory) } usage := info.Usage{} usage.Cpu = cpu.GetAllPercentiles() usage.Memory = memory.GetAllPercentiles() return usage } // Calculate part of a minute this sample set represent. func getPercentComplete(stats []*secondSample) (percent int32) { numSamples := len(stats) if numSamples > 1 { percent = 100 timeRange := stats[numSamples-1].Timestamp.Sub(stats[0].Timestamp).Nanoseconds() // allow some slack if timeRange < 58*secondsToNanoSeconds { percent = int32((timeRange * 100) / 60 * secondsToNanoSeconds) } } return } // Calculate cpurate from two consecutive total cpu usage samples. func getCpuRate(latest, previous secondSample) (uint64, error) { var elapsed int64 elapsed = latest.Timestamp.Sub(previous.Timestamp).Nanoseconds() if elapsed < 10*milliSecondsToNanoSeconds { return 0, fmt.Errorf("elapsed time too small: %d ns: time now %s last %s", elapsed, latest.Timestamp.String(), previous.Timestamp.String()) } if latest.Cpu < previous.Cpu { return 0, fmt.Errorf("bad sample: cumulative cpu usage dropped from %d to %d", latest.Cpu, previous.Cpu) } // Cpurate is calculated in cpu-milliseconds per second. cpuRate := (latest.Cpu - previous.Cpu) * secondsToMilliSeconds / uint64(elapsed) return cpuRate, nil } // Returns a percentile sample for a minute by aggregating seconds samples. func GetMinutePercentiles(stats []*secondSample) info.Usage { lastSample := secondSample{} cpu := NewResource(len(stats)) memory := NewResource(len(stats)) for _, stat := range stats { if !lastSample.Timestamp.IsZero() { cpuRate, err := getCpuRate(*stat, lastSample) if err != nil { continue } cpu.AddSample(cpuRate) memory.AddSample(stat.Memory) } else { memory.AddSample(stat.Memory) } lastSample = *stat } percent := getPercentComplete(stats) return info.Usage{ PercentComplete: percent, Cpu: cpu.GetAllPercentiles(), Memory: memory.GetAllPercentiles(), } } cadvisor-0.27.1/summary/percentiles_test.go000066400000000000000000000122351315410276000210150ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package summary import ( "testing" "time" info "github.com/google/cadvisor/info/v2" ) const Nanosecond = 1000000000 func assertPercentile(t *testing.T, s Uint64Slice, f float64, want uint64) { if got := s.GetPercentile(f); got != want { t.Errorf("GetPercentile(%f) is %d, should be %d.", f, got, want) } } func TestPercentile(t *testing.T) { N := 100 s := make(Uint64Slice, 0, N) for i := N; i > 0; i-- { s = append(s, uint64(i)) } assertPercentile(t, s, 0.2, 20) assertPercentile(t, s, 0.7, 70) assertPercentile(t, s, 0.9, 90) N = 105 for i := 101; i <= N; i++ { s = append(s, uint64(i)) } // 90p should be between 94 and 95. Promoted to 95. assertPercentile(t, s, 0.2, 21) assertPercentile(t, s, 0.7, 74) assertPercentile(t, s, 0.9, 95) } func TestMean(t *testing.T) { var i, N uint64 N = 100 mean := mean{count: 0, Mean: 0} for i = 1; i < N; i++ { mean.Add(i) } if mean.Mean != 50.0 { t.Errorf("Mean is %f, should be 50.0", mean.Mean) } } func TestAggregates(t *testing.T) { N := uint64(100) var i uint64 ct := time.Now() stats := make([]*secondSample, 0, N) for i = 1; i < N; i++ { s := &secondSample{ Timestamp: ct.Add(time.Duration(i) * time.Second), // cpu rate is 1 s/s Cpu: i * Nanosecond, // Memory grows by a KB every second. Memory: i * 1024, } stats = append(stats, s) } usage := GetMinutePercentiles(stats) // Cpu mean, max, and 90p should all be 1000 ms/s. cpuExpected := info.Percentiles{ Present: true, Mean: 1000, Max: 1000, Fifty: 1000, Ninety: 1000, NinetyFive: 1000, } if usage.Cpu != cpuExpected { t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) } memExpected := info.Percentiles{ Present: true, Mean: 50 * 1024, Max: 99 * 1024, Fifty: 50 * 1024, Ninety: 90 * 1024, NinetyFive: 95 * 1024, } if usage.Memory != memExpected { t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected) } } func TestSamplesCloseInTimeIgnored(t *testing.T) { N := uint64(100) var i uint64 ct := time.Now() stats := make([]*secondSample, 0, N*2) for i = 1; i < N; i++ { s1 := &secondSample{ Timestamp: ct.Add(time.Duration(i) * time.Second), // cpu rate is 1 s/s Cpu: i * Nanosecond, // Memory grows by a KB every second. Memory: i * 1024, } stats = append(stats, s1) // Add another dummy sample too close in time to the last one. s2 := &secondSample{ // Add extra millisecond. Timestamp: ct.Add(time.Duration(i) * time.Second).Add(time.Duration(1) * time.Millisecond), Cpu: i * 100 * Nanosecond, Memory: i * 1024 * 1024, } stats = append(stats, s2) } usage := GetMinutePercentiles(stats) // Cpu mean, max, and 90p should all be 1000 ms/s. All high-value samples are discarded. cpuExpected := info.Percentiles{ Present: true, Mean: 1000, Max: 1000, Fifty: 1000, Ninety: 1000, NinetyFive: 1000, } if usage.Cpu != cpuExpected { t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) } memExpected := info.Percentiles{ Present: true, Mean: 50 * 1024, Max: 99 * 1024, Fifty: 50 * 1024, Ninety: 90 * 1024, NinetyFive: 95 * 1024, } if usage.Memory != memExpected { t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected) } } func TestDerivedStats(t *testing.T) { N := uint64(100) var i uint64 stats := make([]*info.Usage, 0, N) for i = 1; i < N; i++ { s := &info.Usage{ PercentComplete: 100, Cpu: info.Percentiles{ Present: true, Mean: i * Nanosecond, Max: i * Nanosecond, Fifty: i * Nanosecond, Ninety: i * Nanosecond, NinetyFive: i * Nanosecond, }, Memory: info.Percentiles{ Present: true, Mean: i * 1024, Max: i * 1024, Fifty: i * 1024, Ninety: i * 1024, NinetyFive: i * 1024, }, } stats = append(stats, s) } usage := GetDerivedPercentiles(stats) cpuExpected := info.Percentiles{ Present: true, Mean: 50 * Nanosecond, Max: 99 * Nanosecond, Fifty: 50 * Nanosecond, Ninety: 90 * Nanosecond, NinetyFive: 95 * Nanosecond, } if usage.Cpu != cpuExpected { t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected) } memExpected := info.Percentiles{ Present: true, Mean: 50 * 1024, Max: 99 * 1024, Fifty: 50 * 1024, Ninety: 90 * 1024, NinetyFive: 95 * 1024, } if usage.Memory != memExpected { t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected) } } cadvisor-0.27.1/summary/summary.go000066400000000000000000000132251315410276000171360ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Maintains the summary of aggregated minute, hour, and day stats. // For a container running for more than a day, amount of tracked data can go up to // 40 KB when cpu and memory are tracked. We'll start by enabling collection for the // node, followed by docker, and then all containers as we understand the usage pattern // better // TODO(rjnagal): Optimize the size if we start running it for every container. package summary import ( "fmt" "sync" "time" "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v2" ) // Usage fields we track for generating percentiles. type secondSample struct { Timestamp time.Time // time when the sample was recorded. Cpu uint64 // cpu usage Memory uint64 // memory usage } type availableResources struct { Cpu bool Memory bool } type StatsSummary struct { // Resources being tracked for this container. available availableResources // list of second samples. The list is cleared when a new minute samples is generated. secondSamples []*secondSample // minute percentiles. We track 24 * 60 maximum samples. minuteSamples *SamplesBuffer // latest derived instant, minute, hour, and day stats. Instant sample updated every second. // Others updated every minute. derivedStats info.DerivedStats // Guarded by dataLock. dataLock sync.RWMutex } // Adds a new seconds sample. // If enough seconds samples are collected, a minute sample is generated and derived // stats are updated. func (s *StatsSummary) AddSample(stat v1.ContainerStats) error { sample := secondSample{} sample.Timestamp = stat.Timestamp if s.available.Cpu { sample.Cpu = stat.Cpu.Usage.Total } if s.available.Memory { sample.Memory = stat.Memory.WorkingSet } s.secondSamples = append(s.secondSamples, &sample) s.updateLatestUsage() // TODO(jnagal): Use 'available' to avoid unnecessary computation. numSamples := len(s.secondSamples) elapsed := time.Nanosecond if numSamples > 1 { start := s.secondSamples[0].Timestamp end := s.secondSamples[numSamples-1].Timestamp elapsed = end.Sub(start) } if elapsed > 60*time.Second { // Make a minute sample. This works with dynamic housekeeping as long // as we keep max dynamic houskeeping period close to a minute. minuteSample := GetMinutePercentiles(s.secondSamples) // Clear seconds samples. Keep the latest sample for continuity. // Copying and resizing helps avoid slice re-allocation. s.secondSamples[0] = s.secondSamples[numSamples-1] s.secondSamples = s.secondSamples[:1] s.minuteSamples.Add(minuteSample) err := s.updateDerivedStats() if err != nil { return err } } return nil } func (s *StatsSummary) updateLatestUsage() { usage := info.InstantUsage{} numStats := len(s.secondSamples) if numStats < 1 { return } latest := s.secondSamples[numStats-1] usage.Memory = latest.Memory if numStats > 1 { previous := s.secondSamples[numStats-2] cpu, err := getCpuRate(*latest, *previous) if err == nil { usage.Cpu = cpu } } s.dataLock.Lock() defer s.dataLock.Unlock() s.derivedStats.LatestUsage = usage s.derivedStats.Timestamp = latest.Timestamp return } // Generate new derived stats based on current minute stats samples. func (s *StatsSummary) updateDerivedStats() error { derived := info.DerivedStats{} derived.Timestamp = time.Now() minuteSamples := s.minuteSamples.RecentStats(1) if len(minuteSamples) != 1 { return fmt.Errorf("failed to retrieve minute stats") } derived.MinuteUsage = *minuteSamples[0] hourUsage, err := s.getDerivedUsage(60) if err != nil { return fmt.Errorf("failed to compute hour stats: %v", err) } dayUsage, err := s.getDerivedUsage(60 * 24) if err != nil { return fmt.Errorf("failed to compute day usage: %v", err) } derived.HourUsage = hourUsage derived.DayUsage = dayUsage s.dataLock.Lock() defer s.dataLock.Unlock() derived.LatestUsage = s.derivedStats.LatestUsage s.derivedStats = derived return nil } // helper method to get hour and daily derived stats func (s *StatsSummary) getDerivedUsage(n int) (info.Usage, error) { if n < 1 { return info.Usage{}, fmt.Errorf("invalid number of samples requested: %d", n) } samples := s.minuteSamples.RecentStats(n) numSamples := len(samples) if numSamples < 1 { return info.Usage{}, fmt.Errorf("failed to retrieve any minute stats.") } // We generate derived stats even with partial data. usage := GetDerivedPercentiles(samples) // Assumes we have equally placed minute samples. usage.PercentComplete = int32(numSamples * 100 / n) return usage, nil } // Return the latest calculated derived stats. func (s *StatsSummary) DerivedStats() (info.DerivedStats, error) { s.dataLock.RLock() defer s.dataLock.RUnlock() return s.derivedStats, nil } func New(spec v1.ContainerSpec) (*StatsSummary, error) { summary := StatsSummary{} if spec.HasCpu { summary.available.Cpu = true } if spec.HasMemory { summary.available.Memory = true } if !summary.available.Cpu && !summary.available.Memory { return nil, fmt.Errorf("none of the resources are being tracked.") } summary.minuteSamples = NewSamplesBuffer(60 /* one hour */) return &summary, nil } cadvisor-0.27.1/test.htdigest000066400000000000000000000000611315410276000161230ustar00rootroot00000000000000admin:localhost:70f2631dded4ce5ad0ebbea5faa6ad6e cadvisor-0.27.1/test.htpasswd000066400000000000000000000000541315410276000161470ustar00rootroot00000000000000admin:$apr1$WVO0Bsre$VrmWGDbcBV1fdAkvgQwdk0 cadvisor-0.27.1/utils/000077500000000000000000000000001315410276000145525ustar00rootroot00000000000000cadvisor-0.27.1/utils/cloudinfo/000077500000000000000000000000001315410276000165345ustar00rootroot00000000000000cadvisor-0.27.1/utils/cloudinfo/aws.go000066400000000000000000000027241315410276000176620ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cloudinfo import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "io/ioutil" "strings" info "github.com/google/cadvisor/info/v1" ) const ( ProductVerFileName = "/sys/class/dmi/id/product_version" Amazon = "amazon" ) func onAWS() bool { data, err := ioutil.ReadFile(ProductVerFileName) if err != nil { return false } return strings.Contains(string(data), Amazon) } func getAwsMetadata(name string) string { client := ec2metadata.New(session.New(&aws.Config{})) data, err := client.GetMetadata(name) if err != nil { return info.UnknownInstance } return data } func getAwsInstanceType() info.InstanceType { return info.InstanceType(getAwsMetadata("instance-type")) } func getAwsInstanceID() info.InstanceID { return info.InstanceID(getAwsMetadata("instance-id")) } cadvisor-0.27.1/utils/cloudinfo/azure.go000066400000000000000000000025241315410276000202140ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cloudinfo import ( info "github.com/google/cadvisor/info/v1" "io/ioutil" "strings" ) const ( SysVendorFileName = "/sys/class/dmi/id/sys_vendor" BiosUUIDFileName = "/sys/class/dmi/id/product_uuid" MicrosoftCorporation = "Microsoft Corporation" ) func onAzure() bool { data, err := ioutil.ReadFile(SysVendorFileName) if err != nil { return false } return strings.Contains(string(data), MicrosoftCorporation) } // TODO: Implement method. func getAzureInstanceType() info.InstanceType { return info.UnknownInstance } func getAzureInstanceID() info.InstanceID { data, err := ioutil.ReadFile(BiosUUIDFileName) if err != nil { return info.UnNamedInstance } return info.InstanceID(strings.TrimSuffix(string(data), "\n")) } cadvisor-0.27.1/utils/cloudinfo/cloudinfo.go000066400000000000000000000050021315410276000210420ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Get information about the cloud provider (if any) cAdvisor is running on. package cloudinfo import ( info "github.com/google/cadvisor/info/v1" ) type CloudInfo interface { GetCloudProvider() info.CloudProvider GetInstanceType() info.InstanceType GetInstanceID() info.InstanceID } type realCloudInfo struct { cloudProvider info.CloudProvider instanceType info.InstanceType instanceID info.InstanceID } func NewRealCloudInfo() CloudInfo { cloudProvider := detectCloudProvider() instanceType := detectInstanceType(cloudProvider) instanceID := detectInstanceID(cloudProvider) return &realCloudInfo{ cloudProvider: cloudProvider, instanceType: instanceType, instanceID: instanceID, } } func (self *realCloudInfo) GetCloudProvider() info.CloudProvider { return self.cloudProvider } func (self *realCloudInfo) GetInstanceType() info.InstanceType { return self.instanceType } func (self *realCloudInfo) GetInstanceID() info.InstanceID { return self.instanceID } func detectCloudProvider() info.CloudProvider { switch { case onGCE(): return info.GCE case onAWS(): return info.AWS case onAzure(): return info.Azure case onBaremetal(): return info.Baremetal } return info.UnknownProvider } func detectInstanceType(cloudProvider info.CloudProvider) info.InstanceType { switch cloudProvider { case info.GCE: return getGceInstanceType() case info.AWS: return getAwsInstanceType() case info.Azure: return getAzureInstanceType() case info.Baremetal: return info.NoInstance } return info.UnknownInstance } func detectInstanceID(cloudProvider info.CloudProvider) info.InstanceID { switch cloudProvider { case info.GCE: return getGceInstanceID() case info.AWS: return getAwsInstanceID() case info.Azure: return getAzureInstanceID() case info.Baremetal: return info.UnNamedInstance } return info.UnNamedInstance } // TODO: Implement method. func onBaremetal() bool { return false } cadvisor-0.27.1/utils/cloudinfo/gce.go000066400000000000000000000031001315410276000176130ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cloudinfo import ( "io/ioutil" "strings" info "github.com/google/cadvisor/info/v1" "cloud.google.com/go/compute/metadata" "github.com/golang/glog" ) const ( gceProductName = "/sys/class/dmi/id/product_name" google = "Google" ) func onGCE() bool { data, err := ioutil.ReadFile(gceProductName) if err != nil { glog.V(2).Infof("Error while reading product_name: %v", err) return false } return strings.Contains(string(data), google) } func getGceInstanceType() info.InstanceType { machineType, err := metadata.Get("instance/machine-type") if err != nil { return info.UnknownInstance } responseParts := strings.Split(machineType, "/") // Extract the instance name from the machine type. return info.InstanceType(responseParts[len(responseParts)-1]) } func getGceInstanceID() info.InstanceID { instanceID, err := metadata.Get("instance/id") if err != nil { return info.UnknownInstance } return info.InstanceID(info.InstanceType(instanceID)) } cadvisor-0.27.1/utils/container/000077500000000000000000000000001315410276000165345ustar00rootroot00000000000000cadvisor-0.27.1/utils/container/container.go000066400000000000000000000020161315410276000210440ustar00rootroot00000000000000// Copyright 2016 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 ( info "github.com/google/cadvisor/info/v1" ) // Returns the alias a container is known by within a certain namespace, // if available. Otherwise returns the absolute name of the container. func GetPreferredName(ref info.ContainerReference) string { var containerName string if len(ref.Aliases) > 0 { containerName = ref.Aliases[0] } else { containerName = ref.Name } return containerName } cadvisor-0.27.1/utils/cpuload/000077500000000000000000000000001315410276000162015ustar00rootroot00000000000000cadvisor-0.27.1/utils/cpuload/cpuload.go000066400000000000000000000025551315410276000201660ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cpuload import ( "fmt" info "github.com/google/cadvisor/info/v1" "github.com/golang/glog" "github.com/google/cadvisor/utils/cpuload/netlink" ) type CpuLoadReader interface { // Start the reader. Start() error // Stop the reader and clean up internal state. Stop() // Retrieve Cpu load for a given group. // name is the full hierarchical name of the container. // Path is an absolute filesystem path for a container under CPU cgroup hierarchy. GetCpuLoad(name string, path string) (info.LoadStats, error) } func New() (CpuLoadReader, error) { reader, err := netlink.New() if err != nil { return nil, fmt.Errorf("failed to create a netlink based cpuload reader: %v", err) } glog.V(3).Info("Using a netlink-based load reader") return reader, nil } cadvisor-0.27.1/utils/cpuload/netlink/000077500000000000000000000000001315410276000176455ustar00rootroot00000000000000cadvisor-0.27.1/utils/cpuload/netlink/conn.go000066400000000000000000000045631315410276000211410ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package netlink import ( "bufio" "bytes" "encoding/binary" "os" "syscall" ) type Connection struct { // netlink socket fd int // cache pid to use in every netlink request. pid uint32 // sequence number for netlink messages. seq uint32 addr syscall.SockaddrNetlink rbuf *bufio.Reader } // Create and bind a new netlink socket. func newConnection() (*Connection, error) { fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_GENERIC) if err != nil { return nil, err } conn := new(Connection) conn.fd = fd conn.seq = 0 conn.pid = uint32(os.Getpid()) conn.addr.Family = syscall.AF_NETLINK conn.rbuf = bufio.NewReader(conn) err = syscall.Bind(fd, &conn.addr) if err != nil { syscall.Close(fd) return nil, err } return conn, err } func (self *Connection) Read(b []byte) (n int, err error) { n, _, err = syscall.Recvfrom(self.fd, b, 0) return n, err } func (self *Connection) Write(b []byte) (n int, err error) { err = syscall.Sendto(self.fd, b, 0, &self.addr) return len(b), err } func (self *Connection) Close() error { return syscall.Close(self.fd) } func (self *Connection) WriteMessage(msg syscall.NetlinkMessage) error { w := bytes.NewBuffer(nil) msg.Header.Len = uint32(syscall.NLMSG_HDRLEN + len(msg.Data)) msg.Header.Seq = self.seq self.seq++ msg.Header.Pid = self.pid binary.Write(w, binary.LittleEndian, msg.Header) _, err := w.Write(msg.Data) if err != nil { return err } _, err = self.Write(w.Bytes()) return err } func (self *Connection) ReadMessage() (msg syscall.NetlinkMessage, err error) { err = binary.Read(self.rbuf, binary.LittleEndian, &msg.Header) if err != nil { return msg, err } msg.Data = make([]byte, msg.Header.Len-syscall.NLMSG_HDRLEN) _, err = self.rbuf.Read(msg.Data) return msg, err } cadvisor-0.27.1/utils/cpuload/netlink/defs.go000066400000000000000000000013741315410276000211220ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package netlink /* #include */ import "C" type TaskStats C.struct_taskstats const ( __TASKSTATS_CMD_MAX = C.__TASKSTATS_CMD_MAX ) cadvisor-0.27.1/utils/cpuload/netlink/example/000077500000000000000000000000001315410276000213005ustar00rootroot00000000000000cadvisor-0.27.1/utils/cpuload/netlink/example/example.go000066400000000000000000000022051315410276000232610ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "log" "github.com/google/cadvisor/utils/cpuload/netlink" ) func main() { n, err := netlink.New() if err != nil { log.Printf("Failed to create cpu load util: %s", err) return } defer n.Stop() paths := []string{"/sys/fs/cgroup/cpu", "/sys/fs/cgroup/cpu/docker"} names := []string{"/", "/docker"} for i, path := range paths { stats, err := n.GetCpuLoad(names[i], path) if err != nil { log.Printf("Error getting cpu load for %q: %s", path, err) } log.Printf("Task load for %s: %+v", path, stats) } } cadvisor-0.27.1/utils/cpuload/netlink/netlink.go000066400000000000000000000152601315410276000216440ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package netlink import ( "bytes" "encoding/binary" "fmt" "os" "syscall" info "github.com/google/cadvisor/info/v1" ) const ( // Kernel constants for tasks stats. genlIdCtrl = syscall.NLMSG_MIN_TYPE // GENL_ID_CTRL taskstatsGenlName = "TASKSTATS" // TASKSTATS_GENL_NAME cgroupStatsCmdAttrFd = 0x1 // CGROUPSTATS_CMD_ATTR_FD ctrlAttrFamilyId = 0x1 // CTRL_ATTR_FAMILY_ID ctrlAttrFamilyName = 0x2 // CTRL_ATTR_FAMILY_NAME ctrlCmdGetFamily = 0x3 // CTRL_CMD_GETFAMILY ) var ( // TODO(rjnagal): Verify and fix for other architectures. Endian = binary.LittleEndian ) type genMsghdr struct { Command uint8 Version uint8 Reserved uint16 } type netlinkMessage struct { Header syscall.NlMsghdr GenHeader genMsghdr Data []byte } func (self netlinkMessage) toRawMsg() (rawmsg syscall.NetlinkMessage) { rawmsg.Header = self.Header w := bytes.NewBuffer([]byte{}) binary.Write(w, Endian, self.GenHeader) w.Write(self.Data) rawmsg.Data = w.Bytes() return rawmsg } type loadStatsResp struct { Header syscall.NlMsghdr GenHeader genMsghdr Stats info.LoadStats } // Return required padding to align 'size' to 'alignment'. func padding(size int, alignment int) int { unalignedPart := size % alignment return (alignment - unalignedPart) % alignment } // Get family id for taskstats subsystem. func getFamilyId(conn *Connection) (uint16, error) { msg := prepareFamilyMessage() conn.WriteMessage(msg.toRawMsg()) resp, err := conn.ReadMessage() if err != nil { return 0, err } id, err := parseFamilyResp(resp) if err != nil { return 0, err } return id, nil } // Append an attribute to the message. // Adds attribute info (length and type), followed by the data and necessary padding. // Can be called multiple times to add attributes. Only fixed size and string type // attributes are handled. We don't need nested attributes for task stats. func addAttribute(buf *bytes.Buffer, attrType uint16, data interface{}, dataSize int) { attr := syscall.RtAttr{ Len: syscall.SizeofRtAttr, Type: attrType, } attr.Len += uint16(dataSize) binary.Write(buf, Endian, attr) switch data := data.(type) { case string: binary.Write(buf, Endian, []byte(data)) buf.WriteByte(0) // terminate default: binary.Write(buf, Endian, data) } for i := 0; i < padding(int(attr.Len), syscall.NLMSG_ALIGNTO); i++ { buf.WriteByte(0) } } // Prepares the message and generic headers and appends attributes as data. func prepareMessage(headerType uint16, cmd uint8, attributes []byte) (msg netlinkMessage) { msg.Header.Type = headerType msg.Header.Flags = syscall.NLM_F_REQUEST msg.GenHeader.Command = cmd msg.GenHeader.Version = 0x1 msg.Data = attributes return msg } // Prepares message to query family id for task stats. func prepareFamilyMessage() (msg netlinkMessage) { buf := bytes.NewBuffer([]byte{}) addAttribute(buf, ctrlAttrFamilyName, taskstatsGenlName, len(taskstatsGenlName)+1) return prepareMessage(genlIdCtrl, ctrlCmdGetFamily, buf.Bytes()) } // Prepares message to query task stats for a task group. func prepareCmdMessage(id uint16, cfd uintptr) (msg netlinkMessage) { buf := bytes.NewBuffer([]byte{}) addAttribute(buf, cgroupStatsCmdAttrFd, uint32(cfd), 4) return prepareMessage(id, __TASKSTATS_CMD_MAX+1, buf.Bytes()) } // Extracts returned family id from the response. func parseFamilyResp(msg syscall.NetlinkMessage) (uint16, error) { m := new(netlinkMessage) m.Header = msg.Header err := verifyHeader(msg) if err != nil { return 0, err } buf := bytes.NewBuffer(msg.Data) // extract generic header from data. err = binary.Read(buf, Endian, &m.GenHeader) if err != nil { return 0, err } id := uint16(0) // Extract attributes. kernel reports family name, id, version, etc. // Scan till we find id. for buf.Len() > syscall.SizeofRtAttr { var attr syscall.RtAttr err = binary.Read(buf, Endian, &attr) if err != nil { return 0, err } if attr.Type == ctrlAttrFamilyId { err = binary.Read(buf, Endian, &id) if err != nil { return 0, err } return id, nil } payload := int(attr.Len) - syscall.SizeofRtAttr skipLen := payload + padding(payload, syscall.SizeofRtAttr) name := make([]byte, skipLen) err = binary.Read(buf, Endian, name) if err != nil { return 0, err } } return 0, fmt.Errorf("family id not found in the response.") } // Extract task stats from response returned by kernel. func parseLoadStatsResp(msg syscall.NetlinkMessage) (*loadStatsResp, error) { m := new(loadStatsResp) m.Header = msg.Header err := verifyHeader(msg) if err != nil { return m, err } buf := bytes.NewBuffer(msg.Data) // Scan the general header. err = binary.Read(buf, Endian, &m.GenHeader) if err != nil { return m, err } // cgroup stats response should have just one attribute. // Read it directly into the stats structure. var attr syscall.RtAttr err = binary.Read(buf, Endian, &attr) if err != nil { return m, err } err = binary.Read(buf, Endian, &m.Stats) if err != nil { return m, err } return m, err } // Verify and return any error reported by kernel. func verifyHeader(msg syscall.NetlinkMessage) error { switch msg.Header.Type { case syscall.NLMSG_DONE: return fmt.Errorf("expected a response, got nil") case syscall.NLMSG_ERROR: buf := bytes.NewBuffer(msg.Data) var errno int32 binary.Read(buf, Endian, errno) return fmt.Errorf("netlink request failed with error %s", syscall.Errno(-errno)) } return nil } // Get load stats for a task group. // id: family id for taskstats. // cfd: open file to path to the cgroup directory under cpu hierarchy. // conn: open netlink connection used to communicate with kernel. func getLoadStats(id uint16, cfd *os.File, conn *Connection) (info.LoadStats, error) { msg := prepareCmdMessage(id, cfd.Fd()) err := conn.WriteMessage(msg.toRawMsg()) if err != nil { return info.LoadStats{}, err } resp, err := conn.ReadMessage() if err != nil { return info.LoadStats{}, err } parsedmsg, err := parseLoadStatsResp(resp) if err != nil { return info.LoadStats{}, err } return parsedmsg.Stats, nil } cadvisor-0.27.1/utils/cpuload/netlink/reader.go000066400000000000000000000042751315410276000214460ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package netlink import ( "fmt" "os" info "github.com/google/cadvisor/info/v1" "github.com/golang/glog" ) type NetlinkReader struct { familyId uint16 conn *Connection } func New() (*NetlinkReader, error) { conn, err := newConnection() if err != nil { return nil, fmt.Errorf("failed to create a new connection: %s", err) } id, err := getFamilyId(conn) if err != nil { return nil, fmt.Errorf("failed to get netlink family id for task stats: %s", err) } glog.V(4).Infof("Family id for taskstats: %d", id) return &NetlinkReader{ familyId: id, conn: conn, }, nil } func (self *NetlinkReader) Stop() { if self.conn != nil { self.conn.Close() } } func (self *NetlinkReader) Start() error { // We do the start setup for netlink in New(). Nothing to do here. return nil } // Returns instantaneous number of running tasks in a group. // Caller can use historical data to calculate cpu load. // path is an absolute filesystem path for a container under the CPU cgroup hierarchy. // NOTE: non-hierarchical load is returned. It does not include load for subcontainers. func (self *NetlinkReader) GetCpuLoad(name string, path string) (info.LoadStats, error) { if len(path) == 0 { return info.LoadStats{}, fmt.Errorf("cgroup path can not be empty!") } cfd, err := os.Open(path) defer cfd.Close() if err != nil { return info.LoadStats{}, fmt.Errorf("failed to open cgroup path %s: %q", path, err) } stats, err := getLoadStats(self.familyId, cfd, self.conn) if err != nil { return info.LoadStats{}, err } glog.V(4).Infof("Task stats for %q: %+v", path, stats) return stats, nil } cadvisor-0.27.1/utils/docker/000077500000000000000000000000001315410276000160215ustar00rootroot00000000000000cadvisor-0.27.1/utils/docker/docker.go000066400000000000000000000041231315410276000176170ustar00rootroot00000000000000// Copyright 2016 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 ( "fmt" "os" "strings" dockertypes "github.com/docker/engine-api/types" ) const ( DockerInfoDriver = "Driver" DockerInfoDriverStatus = "DriverStatus" DriverStatusPoolName = "Pool Name" DriverStatusDataLoopFile = "Data loop file" DriverStatusMetadataFile = "Metadata file" DriverStatusParentDataset = "Parent Dataset" ) func DriverStatusValue(status [][2]string, target string) string { for _, v := range status { if strings.EqualFold(v[0], target) { return v[1] } } return "" } func DockerThinPoolName(info dockertypes.Info) (string, error) { poolName := DriverStatusValue(info.DriverStatus, DriverStatusPoolName) if len(poolName) == 0 { return "", fmt.Errorf("Could not get devicemapper pool name") } return poolName, nil } func DockerMetadataDevice(info dockertypes.Info) (string, error) { metadataDevice := DriverStatusValue(info.DriverStatus, DriverStatusMetadataFile) if len(metadataDevice) != 0 { return metadataDevice, nil } poolName, err := DockerThinPoolName(info) if err != nil { return "", err } metadataDevice = fmt.Sprintf("/dev/mapper/%s_tmeta", poolName) if _, err := os.Stat(metadataDevice); err != nil { return "", err } return metadataDevice, nil } func DockerZfsFilesystem(info dockertypes.Info) (string, error) { filesystem := DriverStatusValue(info.DriverStatus, DriverStatusParentDataset) if len(filesystem) == 0 { return "", fmt.Errorf("Could not get zfs filesystem") } return filesystem, nil } cadvisor-0.27.1/utils/oomparser/000077500000000000000000000000001315410276000165615ustar00rootroot00000000000000cadvisor-0.27.1/utils/oomparser/oomexample/000077500000000000000000000000001315410276000207275ustar00rootroot00000000000000cadvisor-0.27.1/utils/oomparser/oomexample/main.go000066400000000000000000000025261315410276000222070ustar00rootroot00000000000000// 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" "github.com/golang/glog" "github.com/google/cadvisor/utils/oomparser" ) // demonstrates how to run oomparser.OomParser to get OomInstance information func main() { flag.Parse() // out is a user-provided channel from which the user can read incoming // OomInstance objects outStream := make(chan *oomparser.OomInstance) oomLog, err := oomparser.New() if err != nil { glog.Infof("Couldn't make a new oomparser. %v", err) } else { go oomLog.StreamOoms(outStream) // demonstration of how to get oomLog's list of oomInstances or access // the user-declared oomInstance channel, here called outStream for oomInstance := range outStream { glog.Infof("Reading the buffer. Output is %v", oomInstance) } } } cadvisor-0.27.1/utils/oomparser/oomparser.go000066400000000000000000000103611315410276000211200ustar00rootroot00000000000000// 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 oomparser import ( "path" "regexp" "strconv" "time" "github.com/euank/go-kmsg-parser/kmsgparser" "github.com/golang/glog" ) var ( containerRegexp = regexp.MustCompile(`Task in (.*) killed as a result of limit of (.*)`) lastLineRegexp = regexp.MustCompile(`Killed process ([0-9]+) \((.+)\)`) firstLineRegexp = regexp.MustCompile(`invoked oom-killer:`) ) // OomParser wraps a kmsgparser in order to extract OOM events from the // individual kernel ring buffer messages. type OomParser struct { parser kmsgparser.Parser } // struct that contains information related to an OOM kill instance type OomInstance struct { // process id of the killed process Pid int // the name of the killed process ProcessName string // the time that the process was reported to be killed, // accurate to the minute TimeOfDeath time.Time // the absolute name of the container that OOMed ContainerName string // the absolute name of the container that was killed // due to the OOM. VictimContainerName string } // gets the container name from a line and adds it to the oomInstance. func getContainerName(line string, currentOomInstance *OomInstance) error { parsedLine := containerRegexp.FindStringSubmatch(line) if parsedLine == nil { return nil } currentOomInstance.ContainerName = path.Join("/", parsedLine[1]) currentOomInstance.VictimContainerName = path.Join("/", parsedLine[2]) return nil } // gets the pid, name, and date from a line and adds it to oomInstance func getProcessNamePid(line string, currentOomInstance *OomInstance) (bool, error) { reList := lastLineRegexp.FindStringSubmatch(line) if reList == nil { return false, nil } pid, err := strconv.Atoi(reList[1]) if err != nil { return false, err } currentOomInstance.Pid = pid currentOomInstance.ProcessName = reList[2] return true, nil } // uses regex to see if line is the start of a kernel oom log func checkIfStartOfOomMessages(line string) bool { potential_oom_start := firstLineRegexp.MatchString(line) if potential_oom_start { return true } return false } // StreamOoms writes to a provided a stream of OomInstance objects representing // OOM events that are found in the logs. // It will block and should be called from a goroutine. func (self *OomParser) StreamOoms(outStream chan<- *OomInstance) { kmsgEntries := self.parser.Parse() defer self.parser.Close() for msg := range kmsgEntries { in_oom_kernel_log := checkIfStartOfOomMessages(msg.Message) if in_oom_kernel_log { oomCurrentInstance := &OomInstance{ ContainerName: "/", TimeOfDeath: msg.Timestamp, } for msg := range kmsgEntries { err := getContainerName(msg.Message, oomCurrentInstance) if err != nil { glog.Errorf("%v", err) } finished, err := getProcessNamePid(msg.Message, oomCurrentInstance) if err != nil { glog.Errorf("%v", err) } if finished { oomCurrentInstance.TimeOfDeath = msg.Timestamp break } } outStream <- oomCurrentInstance } } // Should not happen glog.Errorf("exiting analyzeLines. OOM events will not be reported.") } // initializes an OomParser object. Returns an OomParser object and an error. func New() (*OomParser, error) { parser, err := kmsgparser.NewParser() if err != nil { return nil, err } parser.SetLogger(glogAdapter{}) return &OomParser{parser: parser}, nil } type glogAdapter struct{} var _ kmsgparser.Logger = glogAdapter{} func (glogAdapter) Infof(format string, args ...interface{}) { glog.V(4).Infof(format, args) } func (glogAdapter) Warningf(format string, args ...interface{}) { glog.Infof(format, args) } func (glogAdapter) Errorf(format string, args ...interface{}) { glog.Warningf(format, args) } cadvisor-0.27.1/utils/oomparser/oomparser_test.go000066400000000000000000000565161315410276000221730ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oomparser import ( "fmt" "testing" "time" "github.com/euank/go-kmsg-parser/kmsgparser" "github.com/stretchr/testify/assert" ) const startLine = "ruby invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0" const endLine = "Killed process 19667 (evil-program2) total-vm:1460016kB, anon-rss:1414008kB, file-rss:4kB" const containerLine = "Task in /mem2 killed as a result of limit of /mem3" const containerLogFile = "containerOomExampleLog.txt" const systemLogFile = "systemOomExampleLog.txt" func createExpectedContainerOomInstance(t *testing.T) *OomInstance { const longForm = "Jan _2 15:04:05 2006" deathTime, err := time.ParseInLocation(longForm, fmt.Sprintf("Jan 5 15:19:27 %d", time.Now().Year()), time.Local) if err != nil { t.Fatalf("could not parse expected time when creating expected container oom instance. Had error %v", err) return nil } return &OomInstance{ Pid: 13536, ProcessName: "memorymonster", TimeOfDeath: deathTime, ContainerName: "/mem2", VictimContainerName: "/mem2", } } func createExpectedSystemOomInstance(t *testing.T) *OomInstance { const longForm = "Jan _2 15:04:05 2006" deathTime, err := time.ParseInLocation(longForm, fmt.Sprintf("Jan 28 19:58:45 %d", time.Now().Year()), time.Local) if err != nil { t.Fatalf("could not parse expected time when creating expected system oom instance. Had error %v", err) return nil } return &OomInstance{ Pid: 1532, ProcessName: "badsysprogram", TimeOfDeath: deathTime, ContainerName: "/", VictimContainerName: "", } } func TestGetContainerName(t *testing.T) { currentOomInstance := new(OomInstance) err := getContainerName(startLine, currentOomInstance) if err != nil { t.Errorf("bad line fed to getContainerName should yield no error, but had error %v", err) } if currentOomInstance.ContainerName != "" { t.Errorf("bad line fed to getContainerName yielded no container name but set it to %s", currentOomInstance.ContainerName) } err = getContainerName(containerLine, currentOomInstance) if err != nil { t.Errorf("container line fed to getContainerName should yield no error, but had error %v", err) } if currentOomInstance.ContainerName != "/mem2" { t.Errorf("getContainerName should have set containerName to /mem2, not %s", currentOomInstance.ContainerName) } if currentOomInstance.VictimContainerName != "/mem3" { t.Errorf("getContainerName should have set victimContainerName to /mem3, not %s", currentOomInstance.VictimContainerName) } } func TestGetProcessNamePid(t *testing.T) { currentOomInstance := new(OomInstance) couldParseLine, err := getProcessNamePid(startLine, currentOomInstance) if err != nil { t.Errorf("bad line fed to getProcessNamePid should yield no error, but had error %v", err) } if couldParseLine { t.Errorf("bad line fed to getProcessNamePid should return false but returned %v", couldParseLine) } couldParseLine, err = getProcessNamePid(endLine, currentOomInstance) if err != nil { t.Errorf("good line fed to getProcessNamePid should yield no error, but had error %v", err) } if !couldParseLine { t.Errorf("good line fed to getProcessNamePid should return true but returned %v", couldParseLine) } if currentOomInstance.ProcessName != "evil-program2" { t.Errorf("getProcessNamePid should have set processName to evil-program2, not %s", currentOomInstance.ProcessName) } if currentOomInstance.Pid != 19667 { t.Errorf("getProcessNamePid should have set PID to 19667, not %d", currentOomInstance.Pid) } } func TestCheckIfStartOfMessages(t *testing.T) { couldParseLine := checkIfStartOfOomMessages(endLine) if couldParseLine { t.Errorf("bad line fed to checkIfStartOfMessages should return false but returned %v", couldParseLine) } couldParseLine = checkIfStartOfOomMessages(startLine) if !couldParseLine { t.Errorf("start line fed to checkIfStartOfMessages should return true but returned %v", couldParseLine) } } func TestLastLineRegex(t *testing.T) { processNames := []string{"foo", "python3.4", "foo-bar", "Plex Media Server", "x86_64-pc-linux-gnu-c++-5.4.0", "[", "()", `"with quotes"`} for _, name := range processNames { line := fmt.Sprintf("Jan 21 22:01:49 localhost kernel: [62279.421192] Killed process 1234 (%s) total-vm:1460016kB, anon-rss:1414008kB, file-rss:4kB", name) oomInfo := &OomInstance{} getProcessNamePid(line, oomInfo) assert.Equal(t, 1234, oomInfo.Pid) assert.Equal(t, name, oomInfo.ProcessName) } } func TestStreamOOMs(t *testing.T) { mockMsgs := make(chan kmsgparser.Message) p := &OomParser{ parser: &mockKmsgParser{ messages: mockMsgs, }, } oomsOut := make(chan *OomInstance) go func() { p.StreamOoms(oomsOut) }() writeAll := func(m []string, t time.Time) { for _, msg := range m { mockMsgs <- kmsgparser.Message{ Message: msg, Timestamp: t, } } } type in struct { msgs []string time time.Time } testTime := time.Unix(0xf331f4ee, 0) testTime2 := time.Unix(0xfa51f001, 0) testPairs := []struct { in []in out []*OomInstance }{ { in: []in{{ time: testTime, msgs: []string{ "memorymonster invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=0", "memorymonster cpuset=/ mems_allowed=0", "CPU: 5 PID: 13536 Comm: memorymonster Tainted: P OX 3.13.0-43-generic #72-Ubuntu", "Hardware name: Hewlett-Packard HP Z420 Workstation/1589, BIOS J61 v03.65 12/19/2013", " ffff88072ae10800 ffff8807a4835c48 ffffffff81720bf6 ffff8807a8e86000", " ffff8807a4835cd0 ffffffff8171b4b1 0000000000000246 ffff88072ae10800", " ffff8807a4835c90 ffff8807a4835ca0 ffffffff811522a7 0000000000000001", "Call Trace:", " [] dump_stack+0x45/0x56", " [] dump_header+0x7f/0x1f1", " [] ? find_lock_task_mm+0x27/0x70", " [] oom_kill_process+0x1ce/0x330", " [] ? security_capable_noaudit+0x15/0x20", " [] mem_cgroup_oom_synchronize+0x51c/0x560", " [] ? mem_cgroup_charge_common+0xa0/0xa0", " [] pagefault_out_of_memory+0x14/0x80", " [] mm_fault_error+0x8e/0x180", " [] __do_page_fault+0x4a1/0x560", " [] ? set_next_entity+0x95/0xb0", " [] ? __switch_to+0x169/0x4c0", " [] do_page_fault+0x1a/0x70", " [] page_fault+0x28/0x30", "Task in /mem2 killed as a result of limit of /mem2", "memory: usage 980kB, limit 980kB, failcnt 4152239", "memory+swap: usage 0kB, limit 18014398509481983kB, failcnt 0", "kmem: usage 0kB, limit 18014398509481983kB, failcnt 0", "Memory cgroup stats for /mem2: cache:0KB rss:980KB rss_huge:0KB mapped_file:0KB writeback:20KB inactive_anon:560KB active_anon:420KB inactive_file:0KB active_file:0KB unevictable:0KB", "[ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name", "[13536] 275858 13536 8389663 343 16267 8324326 0 memorymonster", "Memory cgroup out of memory: Kill process 13536 (memorymonster) score 996 or sacrifice child", "Killed process 13536 (memorymonster) total-vm:33558652kB, anon-rss:920kB, file-rss:452kB", }, }}, out: []*OomInstance{{ TimeOfDeath: testTime, ContainerName: "/mem2", ProcessName: "memorymonster", Pid: 13536, VictimContainerName: "/mem2", }}, }, { in: []in{{ time: testTime, msgs: []string{ "badsysprogram invoked oom-killer: gfp_mask=0x280da, order=0, oom_score_adj=0", "badsysprogram cpuset=/ mems_allowed=0", "CPU: 0 PID: 1532 Comm: badsysprogram Not tainted 3.13.0-27-generic #50-Ubuntu", "Hardware name: Google Google, BIOS Google 01/01/2011", " 0000000000000000 ffff880069715a90 ffffffff817199c4 ffff8800680d8000", " ffff880069715b18 ffffffff817142ff 0000000000000000 0000000000000000", " 0000000000000000 0000000000000000 0000000000000000 0000000000000000", "Call Trace:", " [] dump_stack+0x45/0x56", " [] dump_header+0x7f/0x1f1", " [] oom_kill_process+0x1ce/0x330", " [] ? security_capable_noaudit+0x15/0x20", " [] out_of_memory+0x414/0x450", " [] __alloc_pages_nodemask+0xa87/0xb20", " [] alloc_pages_vma+0x9a/0x140", " [] handle_mm_fault+0xb2b/0xf10", " [] __do_page_fault+0x184/0x560", " [] ? sched_clock+0x9/0x10", " [] ? sched_clock_local+0x1d/0x80", " [] ? acct_account_cputime+0x1c/0x20", " [] ? account_user_time+0x8b/0xa0", " [] ? vtime_account_user+0x54/0x60", " [] do_page_fault+0x1a/0x70", " [] page_fault+0x28/0x30", "Mem-Info:", "Node 0 DMA per-cpu:", "CPU 0: hi: 0, btch: 1 usd: 0", "Node 0 DMA32 per-cpu:", "CPU 0: hi: 186, btch: 31 usd: 86", "active_anon:405991 inactive_anon:57 isolated_anon:0", " active_file:35 inactive_file:69 isolated_file:0", " unevictable:0 dirty:0 writeback:0 unstable:0", " free:12929 slab_reclaimable:1635 slab_unreclaimable:1919", " mapped:34 shmem:70 pagetables:1423 bounce:0", " free_cma:0", "Node 0 DMA free:7124kB min:412kB low:512kB high:616kB active_anon:8508kB inactive_anon:4kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15992kB managed:15908kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:4kB slab_reclaimable:16kB slab_unreclaimable:16kB kernel_stack:0kB pagetables:12kB unstable:0kB bounce:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes", "lowmem_reserve[]: 0 1679 1679 1679", "Node 0 DMA32 free:44592kB min:44640kB low:55800kB high:66960kB active_anon:1615456kB inactive_anon:224kB active_file:140kB inactive_file:276kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:1765368kB managed:1722912kB mlocked:0kB dirty:0kB writeback:0kB mapped:136kB shmem:276kB slab_reclaimable:6524kB slab_unreclaimable:7660kB kernel_stack:592kB pagetables:5680kB unstable:0kB bounce:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:819 all_unreclaimable? yes", "lowmem_reserve[]: 0 0 0 0", "Node 0 DMA: 5*4kB (UM) 6*8kB (UEM) 7*16kB (UEM) 1*32kB (M) 2*64kB (UE) 3*128kB (UEM) 1*256kB (E) 2*512kB (EM) 3*1024kB (UEM) 1*2048kB (R) 0*4096kB = 7124kB", "Node 0 DMA32: 74*4kB (UEM) 125*8kB (UEM) 78*16kB (UEM) 26*32kB (UE) 12*64kB (UEM) 4*128kB (UE) 4*256kB (UE) 2*512kB (E) 11*1024kB (UE) 7*2048kB (UE) 3*4096kB (UR) = 44592kB", "Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB", "204 total pagecache pages", "0 pages in swap cache", "Swap cache stats: add 0, delete 0, find 0/0", "Free swap = 0kB", "Total swap = 0kB", "445340 pages RAM", "0 pages HighMem/MovableOnly", "10614 pages reserved", "[ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name", "[ 273] 0 273 4869 50 13 0 0 upstart-udev-br", "[ 293] 0 293 12802 154 28 0 -1000 systemd-udevd", "[ 321] 0 321 3819 54 12 0 0 upstart-file-br", "[ 326] 102 326 9805 109 24 0 0 dbus-daemon", "[ 334] 101 334 63960 94 26 0 0 rsyslogd", "[ 343] 0 343 10863 102 26 0 0 systemd-logind", "[ 546] 0 546 3815 60 13 0 0 upstart-socket-", "[ 710] 0 710 2556 587 8 0 0 dhclient", "[ 863] 0 863 3955 48 13 0 0 getty", "[ 865] 0 865 3955 50 13 0 0 getty", "[ 867] 0 867 3955 51 13 0 0 getty", "[ 868] 0 868 3955 51 12 0 0 getty", "[ 870] 0 870 3955 49 13 0 0 getty", "[ 915] 0 915 5914 61 16 0 0 cron", "[ 1015] 0 1015 10885 1524 25 0 0 manage_addresse", "[ 1028] 0 1028 3955 49 13 0 0 getty", "[ 1033] 0 1033 3197 48 12 0 0 getty", "[ 1264] 0 1264 11031 1635 26 0 0 manage_accounts", "[ 1268] 0 1268 15341 180 33 0 -1000 sshd", "[ 1313] 104 1313 6804 154 17 0 0 ntpd", "[ 1389] 0 1389 25889 255 55 0 0 sshd", "[ 1407] 1020 1407 25889 255 52 0 0 sshd", "[ 1408] 1020 1408 5711 581 17 0 0 bash", "[ 1425] 0 1425 25889 256 53 0 0 sshd", "[ 1443] 1020 1443 25889 257 52 0 0 sshd", "[ 1444] 1020 1444 5711 581 16 0 0 bash", "[ 1476] 1020 1476 1809 25 9 0 0 tail", "[ 1532] 1020 1532 410347 398810 788 0 0 badsysprogram", "Out of memory: Kill process 1532 (badsysprogram) score 919 or sacrifice child", "Killed process 1532 (badsysprogram) total-vm:1641388kB, anon-rss:1595164kB, file-rss:76kB", }, }}, out: []*OomInstance{{ Pid: 1532, ProcessName: "badsysprogram", TimeOfDeath: testTime, ContainerName: "/", VictimContainerName: "", }}, }, { // Multiple OOMs // These were generated via `docker run -m 20M euank/gunpowder-memhog 2G; docker run -m 300M euank/gunpowder-memhog 800M` // followed by nabbing output from `/dev/kmsg` and stripping the syslog-ish prefixes `kmsgparser` will handle anyways. in: []in{ { time: testTime, msgs: []string{ "docker0: port 2(veth380a1cd) entered disabled state", "device veth380a1cd left promiscuous mode", "docker0: port 2(veth380a1cd) entered disabled state", "docker0: port 2(vethcd0dbfb) entered blocking state", "docker0: port 2(vethcd0dbfb) entered disabled state", "device vethcd0dbfb entered promiscuous mode", "IPv6: ADDRCONF(NETDEV_UP): vethcd0dbfb: link is not ready", "IPv6: ADDRCONF(NETDEV_CHANGE): vethcd0dbfb: link becomes ready", "docker0: port 2(vethcd0dbfb) entered blocking state", "docker0: port 2(vethcd0dbfb) entered forwarding state", "docker0: port 2(vethcd0dbfb) entered disabled state", "eth0: renamed from vethbcd01c4", "docker0: port 2(vethcd0dbfb) entered blocking state", "docker0: port 2(vethcd0dbfb) entered forwarding state", "gunpowder-memho invoked oom-killer: gfp_mask=0x24000c0(GFP_KERNEL), order=0, oom_score_adj=0", "gunpowder-memho cpuset=2e088fe462e25e60be1dafafe2c05c47bda1a97978648d10ad2b7484fc0b8f50 mems_allowed=0", "CPU: 0 PID: 1381 Comm: gunpowder-memho Tainted: G O 4.8.0-gentoo #2", "Hardware name: LENOVO 20BSCTO1WW/20BSCTO1WW, BIOS N14ET32W (1.10 ) 08/13/2015", " 0000000000000000 ffff8800968e3ca0 ffffffff8137ad47 ffff8800968e3d68", " ffff8800b74ee540 ffff8800968e3d00 ffffffff811261dd 0000000000000003", " 0000000000000000 0000000000000001 0000000000000246 0000000000000202", "Call Trace:", " [] dump_stack+0x4d/0x63", " [] dump_header+0x58/0x1c8", " [] oom_kill_process+0x7e/0x362", " [] ? mem_cgroup_iter+0x109/0x23e", " [] mem_cgroup_out_of_memory+0x241/0x299", " [] mem_cgroup_oom_synchronize+0x273/0x28c", " [] ? __mem_cgroup_insert_exceeded+0x76/0x76", " [] pagefault_out_of_memory+0x1f/0x76", " [] mm_fault_error+0x56/0x108", " [] __do_page_fault+0x36b/0x3ee", " [] do_page_fault+0xc/0xe", " [] page_fault+0x22/0x30", "Task in /docker/2e088fe462e25e60be1dafafe2c05c47bda1a97978648d10ad2b7484fc0b8f50 killed as a result of limit of /docker/2e088fe462e25e60be1dafafe2c05c47bda1a97978648d10ad2b7484fc0b8f50", "memory: usage 20480kB, limit 20480kB, failcnt 1204", "memory+swap: usage 40940kB, limit 40960kB, failcnt 6", "kmem: usage 220kB, limit 9007199254740988kB, failcnt 0", "Memory cgroup stats for /docker/2e088fe462e25e60be1dafafe2c05c47bda1a97978648d10ad2b7484fc0b8f50: cache:0KB rss:20260KB rss_huge:0KB mapped_file:0KB dirty:0KB writeback:1016KB swap:20460KB inactive_anon:10232KB active_anon:10028KB inactive_file:0KB active_file:0KB unevictable:0KB", "[ pid ] uid tgid total_vm rss nr_ptes nr_pmds swapents oom_score_adj name", "[ 1381] 0 1381 530382 5191 34 4 5489 0 gunpowder-memho", "Memory cgroup out of memory: Kill process 1381 (gunpowder-memho) score 1046 or sacrifice child", "Killed process 1381 (gunpowder-memho) total-vm:2121528kB, anon-rss:18624kB, file-rss:2140kB, shmem-rss:0kB", "oom_reaper: reaped process 1381 (gunpowder-memho), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB", "docker0: port 2(vethcd0dbfb) entered disabled state", "vethbcd01c4: renamed from eth0", "docker0: port 2(vethcd0dbfb) entered disabled state", "device vethcd0dbfb left promiscuous mode", "docker0: port 2(vethcd0dbfb) entered disabled state", "docker0: port 2(veth4cb51e1) entered blocking state", "docker0: port 2(veth4cb51e1) entered disabled state", "device veth4cb51e1 entered promiscuous mode", }, }, { time: testTime2, msgs: []string{ "IPv6: ADDRCONF(NETDEV_UP): veth4cb51e1: link is not ready", "docker0: port 2(veth4cb51e1) entered blocking state", "docker0: port 2(veth4cb51e1) entered forwarding state", "IPv6: ADDRCONF(NETDEV_CHANGE): veth4cb51e1: link becomes ready", "docker0: port 2(veth4cb51e1) entered disabled state", "eth0: renamed from veth4b89c12", "docker0: port 2(veth4cb51e1) entered blocking state", "docker0: port 2(veth4cb51e1) entered forwarding state", "gunpowder-memho invoked oom-killer: gfp_mask=0x24000c0(GFP_KERNEL), order=0, oom_score_adj=0", "gunpowder-memho cpuset=6c6fcab8562fd3150854986b78552c732f234fd405b624207b8843528a145e70 mems_allowed=0", "CPU: 0 PID: 1667 Comm: gunpowder-memho Tainted: G O 4.8.0-gentoo #2", "Hardware name: LENOVO 20BSCTO1WW/20BSCTO1WW, BIOS N14ET32W (1.10 ) 08/13/2015", " 0000000000000000 ffff88008137fca0 ffffffff8137ad47 ffff88008137fd68", " ffff8801c75b0c40 ffff88008137fd00 ffffffff811261dd 0000000000000003", " 0000000000000000 0000000000000001 0000000000000246 0000000000000202", "Call Trace:", " [] dump_stack+0x4d/0x63", " [] dump_header+0x58/0x1c8", " [] oom_kill_process+0x7e/0x362", " [] ? mem_cgroup_iter+0x109/0x23e", " [] mem_cgroup_out_of_memory+0x241/0x299", " [] mem_cgroup_oom_synchronize+0x273/0x28c", " [] ? __mem_cgroup_insert_exceeded+0x76/0x76", " [] pagefault_out_of_memory+0x1f/0x76", " [] mm_fault_error+0x56/0x108", " [] __do_page_fault+0x36b/0x3ee", " [] do_page_fault+0xc/0xe", " [] page_fault+0x22/0x30", "Task in /docker/6c6fcab8562fd3150854986b78552c732f234fd405b624207b8843528a145e70 killed as a result of limit of /docker/6c6fcab8562fd3150854986b78552c732f234fd405b624207b8843528a145e70", "memory: usage 307112kB, limit 307200kB, failcnt 35982", "memory+swap: usage 614400kB, limit 614400kB, failcnt 11", "kmem: usage 1308kB, limit 9007199254740988kB, failcnt 0", "Memory cgroup stats for /docker/6c6fcab8562fd3150854986b78552c732f234fd405b624207b8843528a145e70: cache:0KB rss:305804KB rss_huge:0KB mapped_file:0KB dirty:0KB writeback:55884KB swap:307288KB inactive_anon:152940KB active_anon:152832KB inactive_file:0KB active_file:0KB unevictable:0KB", "[ pid ] uid tgid total_vm rss nr_ptes nr_pmds swapents oom_score_adj name", "[ 1667] 0 1667 210894 62557 315 4 91187 0 gunpowder-memho", "Memory cgroup out of memory: Kill process 1667 (gunpowder-memho) score 1003 or sacrifice child", "Killed process 1667 (gunpowder-memho) total-vm:843576kB, anon-rss:248180kB, file-rss:2048kB, shmem-rss:0kB", "oom_reaper: reaped process 1667 (gunpowder-memho), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB", "docker0: port 2(veth4cb51e1) entered disabled state", "veth4b89c12: renamed from eth0", "docker0: port 2(veth4cb51e1) entered blocking state", "docker0: port 2(veth4cb51e1) entered forwarding state", "docker0: port 2(veth4cb51e1) entered disabled state", "device veth4cb51e1 left promiscuous mode", "docker0: port 2(veth4cb51e1) entered disabled state", }, }, }, out: []*OomInstance{ { Pid: 1381, ProcessName: "gunpowder-memho", TimeOfDeath: testTime, ContainerName: "/docker/2e088fe462e25e60be1dafafe2c05c47bda1a97978648d10ad2b7484fc0b8f50", VictimContainerName: "/docker/2e088fe462e25e60be1dafafe2c05c47bda1a97978648d10ad2b7484fc0b8f50", }, { Pid: 1667, ProcessName: "gunpowder-memho", TimeOfDeath: testTime2, ContainerName: "/docker/6c6fcab8562fd3150854986b78552c732f234fd405b624207b8843528a145e70", VictimContainerName: "/docker/6c6fcab8562fd3150854986b78552c732f234fd405b624207b8843528a145e70", }, }, }, } for _, pair := range testPairs { go func() { for _, x := range pair.in { writeAll(x.msgs, x.time) } }() for _, expected := range pair.out { oom := <-oomsOut assert.Equal(t, expected, oom) } select { case oom := <-oomsOut: t.Errorf("did not expect any remaining OOMs, got %+v", oom) default: } } } type mockKmsgParser struct { messages chan kmsgparser.Message } func (m *mockKmsgParser) SeekEnd() error { return nil } func (m *mockKmsgParser) Parse() <-chan kmsgparser.Message { return m.messages } func (m *mockKmsgParser) SetLogger(kmsgparser.Logger) {} func (m *mockKmsgParser) Close() error { close(m.messages) return nil } cadvisor-0.27.1/utils/path.go000066400000000000000000000013551315410276000160410ustar00rootroot00000000000000// 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 utils import "os" func FileExists(file string) bool { if _, err := os.Stat(file); err != nil { return false } return true } cadvisor-0.27.1/utils/procfs/000077500000000000000000000000001315410276000160465ustar00rootroot00000000000000cadvisor-0.27.1/utils/procfs/doc.go000066400000000000000000000014171315410276000171450ustar00rootroot00000000000000// 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. // procfs contains several low level functions to read information from /proc // filesystem, and also provides some utility functions like JiffiesToDuration. package procfs cadvisor-0.27.1/utils/procfs/jiffy.go000066400000000000000000000016051315410276000175060ustar00rootroot00000000000000// 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 procfs /* #include */ import "C" import "time" var userHz uint64 func init() { userHzLong := C.sysconf(C._SC_CLK_TCK) userHz = uint64(userHzLong) } func JiffiesToDuration(jiffies uint64) time.Duration { d := jiffies * 1000000000 / userHz return time.Duration(d) } cadvisor-0.27.1/utils/sysfs/000077500000000000000000000000001315410276000157215ustar00rootroot00000000000000cadvisor-0.27.1/utils/sysfs/fakesysfs/000077500000000000000000000000001315410276000177175ustar00rootroot00000000000000cadvisor-0.27.1/utils/sysfs/fakesysfs/fake.go000066400000000000000000000053151315410276000211600ustar00rootroot00000000000000// 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 fakesysfs import ( "os" "time" "github.com/google/cadvisor/utils/sysfs" ) // If we extend sysfs to support more interfaces, it might be worth making this a mock instead of a fake. type FileInfo struct { EntryName string } func (self *FileInfo) Name() string { return self.EntryName } func (self *FileInfo) Size() int64 { return 1234567 } func (self *FileInfo) Mode() os.FileMode { return 0 } func (self *FileInfo) ModTime() time.Time { return time.Time{} } func (self *FileInfo) IsDir() bool { return true } func (self *FileInfo) Sys() interface{} { return nil } type FakeSysFs struct { info FileInfo cache sysfs.CacheInfo } func (self *FakeSysFs) GetBlockDevices() ([]os.FileInfo, error) { self.info.EntryName = "sda" return []os.FileInfo{&self.info}, nil } func (self *FakeSysFs) GetBlockDeviceSize(name string) (string, error) { return "1234567", nil } func (self *FakeSysFs) GetBlockDeviceScheduler(name string) (string, error) { return "noop deadline [cfq]", nil } func (self *FakeSysFs) GetBlockDeviceNumbers(name string) (string, error) { return "8:0\n", nil } func (self *FakeSysFs) GetNetworkDevices() ([]os.FileInfo, error) { return []os.FileInfo{&self.info}, nil } func (self *FakeSysFs) GetNetworkAddress(name string) (string, error) { return "42:01:02:03:04:f4\n", nil } func (self *FakeSysFs) GetNetworkMtu(name string) (string, error) { return "1024\n", nil } func (self *FakeSysFs) GetNetworkSpeed(name string) (string, error) { return "1000\n", nil } func (self *FakeSysFs) GetNetworkStatValue(name string, stat string) (uint64, error) { return 1024, nil } func (self *FakeSysFs) GetCaches(id int) ([]os.FileInfo, error) { self.info.EntryName = "index0" return []os.FileInfo{&self.info}, nil } func (self *FakeSysFs) GetCacheInfo(cpu int, cache string) (sysfs.CacheInfo, error) { return self.cache, nil } func (self *FakeSysFs) SetCacheInfo(cache sysfs.CacheInfo) { self.cache = cache } func (self *FakeSysFs) SetEntryName(name string) { self.info.EntryName = name } func (self *FakeSysFs) GetSystemUUID() (string, error) { return "1F862619-BA9F-4526-8F85-ECEAF0C97430", nil } cadvisor-0.27.1/utils/sysfs/sysfs.go000066400000000000000000000151411315410276000174210ustar00rootroot00000000000000// 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 sysfs import ( "fmt" "io/ioutil" "os" "path" "strconv" "strings" ) const ( blockDir = "/sys/block" cacheDir = "/sys/devices/system/cpu/cpu" netDir = "/sys/class/net" dmiDir = "/sys/class/dmi" ppcDevTree = "/proc/device-tree" s390xDevTree = "/etc" // s390/s390x changes ) type CacheInfo struct { // size in bytes Size uint64 // cache type - instruction, data, unified Type string // distance from cpus in a multi-level hierarchy Level int // number of cpus that can access this cache. Cpus int } // Abstracts the lowest level calls to sysfs. type SysFs interface { // Get directory information for available block devices. GetBlockDevices() ([]os.FileInfo, error) // Get Size of a given block device. GetBlockDeviceSize(string) (string, error) // Get scheduler type for the block device. GetBlockDeviceScheduler(string) (string, error) // Get device major:minor number string. GetBlockDeviceNumbers(string) (string, error) GetNetworkDevices() ([]os.FileInfo, error) GetNetworkAddress(string) (string, error) GetNetworkMtu(string) (string, error) GetNetworkSpeed(string) (string, error) GetNetworkStatValue(dev string, stat string) (uint64, error) // Get directory information for available caches accessible to given cpu. GetCaches(id int) ([]os.FileInfo, error) // Get information for a cache accessible from the given cpu. GetCacheInfo(cpu int, cache string) (CacheInfo, error) GetSystemUUID() (string, error) } type realSysFs struct{} func NewRealSysFs() SysFs { return &realSysFs{} } func (self *realSysFs) GetBlockDevices() ([]os.FileInfo, error) { return ioutil.ReadDir(blockDir) } func (self *realSysFs) GetBlockDeviceNumbers(name string) (string, error) { dev, err := ioutil.ReadFile(path.Join(blockDir, name, "/dev")) if err != nil { return "", err } return string(dev), nil } func (self *realSysFs) GetBlockDeviceScheduler(name string) (string, error) { sched, err := ioutil.ReadFile(path.Join(blockDir, name, "/queue/scheduler")) if err != nil { return "", err } return string(sched), nil } func (self *realSysFs) GetBlockDeviceSize(name string) (string, error) { size, err := ioutil.ReadFile(path.Join(blockDir, name, "/size")) if err != nil { return "", err } return string(size), nil } func (self *realSysFs) GetNetworkDevices() ([]os.FileInfo, error) { files, err := ioutil.ReadDir(netDir) if err != nil { return nil, err } // Filter out non-directory & non-symlink files var dirs []os.FileInfo for _, f := range files { if f.Mode()|os.ModeSymlink != 0 { f, err = os.Stat(path.Join(netDir, f.Name())) if err != nil { continue } } if f.IsDir() { dirs = append(dirs, f) } } return dirs, nil } func (self *realSysFs) GetNetworkAddress(name string) (string, error) { address, err := ioutil.ReadFile(path.Join(netDir, name, "/address")) if err != nil { return "", err } return string(address), nil } func (self *realSysFs) GetNetworkMtu(name string) (string, error) { mtu, err := ioutil.ReadFile(path.Join(netDir, name, "/mtu")) if err != nil { return "", err } return string(mtu), nil } func (self *realSysFs) GetNetworkSpeed(name string) (string, error) { speed, err := ioutil.ReadFile(path.Join(netDir, name, "/speed")) if err != nil { return "", err } return string(speed), nil } func (self *realSysFs) GetNetworkStatValue(dev string, stat string) (uint64, error) { statPath := path.Join(netDir, dev, "/statistics", stat) out, err := ioutil.ReadFile(statPath) if err != nil { return 0, fmt.Errorf("failed to read stat from %q for device %q", statPath, dev) } var s uint64 n, err := fmt.Sscanf(string(out), "%d", &s) if err != nil || n != 1 { return 0, fmt.Errorf("could not parse value from %q for file %s", string(out), statPath) } return s, nil } func (self *realSysFs) GetCaches(id int) ([]os.FileInfo, error) { cpuPath := fmt.Sprintf("%s%d/cache", cacheDir, id) return ioutil.ReadDir(cpuPath) } func bitCount(i uint64) (count int) { for i != 0 { if i&1 == 1 { count++ } i >>= 1 } return } func getCpuCount(cache string) (count int, err error) { out, err := ioutil.ReadFile(path.Join(cache, "/shared_cpu_map")) if err != nil { return 0, err } masks := strings.Split(string(out), ",") for _, mask := range masks { // convert hex string to uint64 m, err := strconv.ParseUint(strings.TrimSpace(mask), 16, 64) if err != nil { return 0, fmt.Errorf("failed to parse cpu map %q: %v", string(out), err) } count += bitCount(m) } return } func (self *realSysFs) GetCacheInfo(id int, name string) (CacheInfo, error) { cachePath := fmt.Sprintf("%s%d/cache/%s", cacheDir, id, name) out, err := ioutil.ReadFile(path.Join(cachePath, "/size")) if err != nil { return CacheInfo{}, err } var size uint64 n, err := fmt.Sscanf(string(out), "%dK", &size) if err != nil || n != 1 { return CacheInfo{}, err } // convert to bytes size = size * 1024 out, err = ioutil.ReadFile(path.Join(cachePath, "/level")) if err != nil { return CacheInfo{}, err } var level int n, err = fmt.Sscanf(string(out), "%d", &level) if err != nil || n != 1 { return CacheInfo{}, err } out, err = ioutil.ReadFile(path.Join(cachePath, "/type")) if err != nil { return CacheInfo{}, err } cacheType := strings.TrimSpace(string(out)) cpuCount, err := getCpuCount(cachePath) if err != nil { return CacheInfo{}, err } return CacheInfo{ Size: size, Level: level, Type: cacheType, Cpus: cpuCount, }, nil } func (self *realSysFs) GetSystemUUID() (string, error) { if id, err := ioutil.ReadFile(path.Join(dmiDir, "id", "product_uuid")); err == nil { return strings.TrimSpace(string(id)), nil } else if id, err = ioutil.ReadFile(path.Join(ppcDevTree, "system-id")); err == nil { return strings.TrimSpace(string(id)), nil } else if id, err = ioutil.ReadFile(path.Join(ppcDevTree, "vm,uuid")); err == nil { return strings.TrimSpace(string(id)), nil } else if id, err = ioutil.ReadFile(path.Join(s390xDevTree, "machine-id")); err == nil { return strings.TrimSpace(string(id)), nil } else { return "", err } } cadvisor-0.27.1/utils/sysinfo/000077500000000000000000000000001315410276000162445ustar00rootroot00000000000000cadvisor-0.27.1/utils/sysinfo/sysinfo.go000066400000000000000000000127111315410276000202670ustar00rootroot00000000000000// 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 sysinfo import ( "fmt" "regexp" "strconv" "strings" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/utils/sysfs" ) var schedulerRegExp = regexp.MustCompile(`.*\[(.*)\].*`) // Get information about block devices present on the system. // Uses the passed in system interface to retrieve the low level OS information. func GetBlockDeviceInfo(sysfs sysfs.SysFs) (map[string]info.DiskInfo, error) { disks, err := sysfs.GetBlockDevices() if err != nil { return nil, err } diskMap := make(map[string]info.DiskInfo) for _, disk := range disks { name := disk.Name() // Ignore non-disk devices. // TODO(rjnagal): Maybe just match hd, sd, and dm prefixes. if strings.HasPrefix(name, "loop") || strings.HasPrefix(name, "ram") || strings.HasPrefix(name, "sr") { continue } disk_info := info.DiskInfo{ Name: name, } dev, err := sysfs.GetBlockDeviceNumbers(name) if err != nil { return nil, err } n, err := fmt.Sscanf(dev, "%d:%d", &disk_info.Major, &disk_info.Minor) if err != nil || n != 2 { return nil, fmt.Errorf("could not parse device numbers from %s for device %s", dev, name) } out, err := sysfs.GetBlockDeviceSize(name) if err != nil { return nil, err } // Remove trailing newline before conversion. size, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64) if err != nil { return nil, err } // size is in 512 bytes blocks. disk_info.Size = size * 512 disk_info.Scheduler = "none" blkSched, err := sysfs.GetBlockDeviceScheduler(name) if err == nil { matches := schedulerRegExp.FindSubmatch([]byte(blkSched)) if len(matches) >= 2 { disk_info.Scheduler = string(matches[1]) } } device := fmt.Sprintf("%d:%d", disk_info.Major, disk_info.Minor) diskMap[device] = disk_info } return diskMap, nil } // Get information about network devices present on the system. func GetNetworkDevices(sysfs sysfs.SysFs) ([]info.NetInfo, error) { devs, err := sysfs.GetNetworkDevices() if err != nil { return nil, err } netDevices := []info.NetInfo{} for _, dev := range devs { name := dev.Name() // Ignore docker, loopback, and veth devices. ignoredDevices := []string{"lo", "veth", "docker"} ignored := false for _, prefix := range ignoredDevices { if strings.HasPrefix(name, prefix) { ignored = true break } } if ignored { continue } address, err := sysfs.GetNetworkAddress(name) if err != nil { return nil, err } mtuStr, err := sysfs.GetNetworkMtu(name) if err != nil { return nil, err } var mtu int64 n, err := fmt.Sscanf(mtuStr, "%d", &mtu) if err != nil || n != 1 { return nil, fmt.Errorf("could not parse mtu from %s for device %s", mtuStr, name) } netInfo := info.NetInfo{ Name: name, MacAddress: strings.TrimSpace(address), Mtu: mtu, } speed, err := sysfs.GetNetworkSpeed(name) // Some devices don't set speed. if err == nil { var s int64 n, err := fmt.Sscanf(speed, "%d", &s) if err != nil || n != 1 { return nil, fmt.Errorf("could not parse speed from %s for device %s", speed, name) } netInfo.Speed = s } netDevices = append(netDevices, netInfo) } return netDevices, nil } func GetCacheInfo(sysFs sysfs.SysFs, id int) ([]sysfs.CacheInfo, error) { caches, err := sysFs.GetCaches(id) if err != nil { return nil, err } info := []sysfs.CacheInfo{} for _, cache := range caches { if !strings.HasPrefix(cache.Name(), "index") { continue } cacheInfo, err := sysFs.GetCacheInfo(id, cache.Name()) if err != nil { return nil, err } info = append(info, cacheInfo) } return info, nil } func GetNetworkStats(name string) (info.InterfaceStats, error) { // TODO(rjnagal): Take syfs as an argument. sysFs := sysfs.NewRealSysFs() return getNetworkStats(name, sysFs) } func getNetworkStats(name string, sysFs sysfs.SysFs) (info.InterfaceStats, error) { var stats info.InterfaceStats var err error stats.Name = name stats.RxBytes, err = sysFs.GetNetworkStatValue(name, "rx_bytes") if err != nil { return stats, err } stats.RxPackets, err = sysFs.GetNetworkStatValue(name, "rx_packets") if err != nil { return stats, err } stats.RxErrors, err = sysFs.GetNetworkStatValue(name, "rx_errors") if err != nil { return stats, err } stats.RxDropped, err = sysFs.GetNetworkStatValue(name, "rx_dropped") if err != nil { return stats, err } stats.TxBytes, err = sysFs.GetNetworkStatValue(name, "tx_bytes") if err != nil { return stats, err } stats.TxPackets, err = sysFs.GetNetworkStatValue(name, "tx_packets") if err != nil { return stats, err } stats.TxErrors, err = sysFs.GetNetworkStatValue(name, "tx_errors") if err != nil { return stats, err } stats.TxDropped, err = sysFs.GetNetworkStatValue(name, "tx_dropped") if err != nil { return stats, err } return stats, nil } func GetSystemUUID(sysFs sysfs.SysFs) (string, error) { return sysFs.GetSystemUUID() } cadvisor-0.27.1/utils/sysinfo/sysinfo_test.go000066400000000000000000000074371315410276000213370ustar00rootroot00000000000000// 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 sysinfo import ( "testing" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/utils/sysfs/fakesysfs" ) func TestGetBlockDeviceInfo(t *testing.T) { fakeSys := fakesysfs.FakeSysFs{} disks, err := GetBlockDeviceInfo(&fakeSys) if err != nil { t.Errorf("expected call to GetBlockDeviceInfo() to succeed. Failed with %s", err) } if len(disks) != 1 { t.Errorf("expected to get one disk entry. Got %d", len(disks)) } key := "8:0" disk, ok := disks[key] if !ok { t.Fatalf("expected key 8:0 to exist in the disk map.") } if disk.Name != "sda" { t.Errorf("expected to get disk named sda. Got %q", disk.Name) } size := uint64(1234567 * 512) if disk.Size != size { t.Errorf("expected to get disk size of %d. Got %d", size, disk.Size) } if disk.Scheduler != "cfq" { t.Errorf("expected to get scheduler type of cfq. Got %q", disk.Scheduler) } } func TestGetNetworkDevices(t *testing.T) { fakeSys := fakesysfs.FakeSysFs{} fakeSys.SetEntryName("eth0") devs, err := GetNetworkDevices(&fakeSys) if err != nil { t.Errorf("expected call to GetNetworkDevices() to succeed. Failed with %s", err) } if len(devs) != 1 { t.Errorf("expected to get one network device. Got %d", len(devs)) } eth := devs[0] if eth.Name != "eth0" { t.Errorf("expected to find device with name eth0. Found name %q", eth.Name) } if eth.Mtu != 1024 { t.Errorf("expected mtu to be set to 1024. Found %d", eth.Mtu) } if eth.Speed != 1000 { t.Errorf("expected device speed to be set to 1000. Found %d", eth.Speed) } if eth.MacAddress != "42:01:02:03:04:f4" { t.Errorf("expected mac address to be '42:01:02:03:04:f4'. Found %q", eth.MacAddress) } } func TestIgnoredNetworkDevices(t *testing.T) { fakeSys := fakesysfs.FakeSysFs{} ignoredDevices := []string{"veth1234", "lo", "docker0"} for _, name := range ignoredDevices { fakeSys.SetEntryName(name) devs, err := GetNetworkDevices(&fakeSys) if err != nil { t.Errorf("expected call to GetNetworkDevices() to succeed. Failed with %s", err) } if len(devs) != 0 { t.Errorf("expected dev %s to be ignored, but got info %+v", name, devs) } } } func TestGetCacheInfo(t *testing.T) { fakeSys := &fakesysfs.FakeSysFs{} cacheInfo := sysfs.CacheInfo{ Size: 1024, Type: "Data", Level: 3, Cpus: 16, } fakeSys.SetCacheInfo(cacheInfo) caches, err := GetCacheInfo(fakeSys, 0) if err != nil { t.Errorf("expected call to GetCacheInfo() to succeed. Failed with %s", err) } if len(caches) != 1 { t.Errorf("expected to get one cache. Got %d", len(caches)) } if caches[0] != cacheInfo { t.Errorf("expected to find cacheinfo %+v. Got %+v", cacheInfo, caches[0]) } } func TestGetNetworkStats(t *testing.T) { expected_stats := info.InterfaceStats{ Name: "eth0", RxBytes: 1024, RxPackets: 1024, RxErrors: 1024, RxDropped: 1024, TxBytes: 1024, TxPackets: 1024, TxErrors: 1024, TxDropped: 1024, } fakeSys := &fakesysfs.FakeSysFs{} netStats, err := getNetworkStats("eth0", fakeSys) if err != nil { t.Errorf("call to getNetworkStats() failed with %s", err) } if expected_stats != netStats { t.Errorf("expected to get stats %+v, got %+v", expected_stats, netStats) } } cadvisor-0.27.1/utils/tail/000077500000000000000000000000001315410276000155035ustar00rootroot00000000000000cadvisor-0.27.1/utils/tail/tail.go000066400000000000000000000073471315410276000167760ustar00rootroot00000000000000// Copyright 2016 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 tail implements "tail -F" functionality following rotated logs package tail import ( "bufio" "fmt" "io" "os" "path/filepath" "sync" "time" "github.com/golang/glog" "golang.org/x/exp/inotify" ) type Tail struct { reader *bufio.Reader readerErr error readerLock sync.RWMutex filename string file *os.File stop chan bool watcher *inotify.Watcher } const ( defaultRetryInterval = 100 * time.Millisecond maxRetryInterval = 30 * time.Second ) // NewTail starts opens the given file and watches it for deletion/rotation func NewTail(filename string) (*Tail, error) { t, err := newTail(filename) if err != nil { return nil, err } go t.watchLoop() return t, nil } // newTail creates a Tail object. func newTail(filename string) (*Tail, error) { t := &Tail{ filename: filename, } var err error t.stop = make(chan bool) t.watcher, err = inotify.NewWatcher() if err != nil { return nil, fmt.Errorf("inotify init failed on %s: %v", t.filename, err) } // Initialize readerErr as io.EOF, so that the reader can work properly // during initialization. t.readerErr = io.EOF return t, nil } // Read implements the io.Reader interface for Tail func (t *Tail) Read(p []byte) (int, error) { t.readerLock.RLock() defer t.readerLock.RUnlock() if t.readerErr != nil { return 0, t.readerErr } return t.reader.Read(p) } var _ io.ReadCloser = &Tail{} // Close stops watching and closes the file func (t *Tail) Close() error { close(t.stop) return nil } func (t *Tail) attemptOpen() error { t.readerLock.Lock() defer t.readerLock.Unlock() t.readerErr = nil attempt := 0 var lastErr error for interval := defaultRetryInterval; ; interval *= 2 { attempt++ glog.V(4).Infof("Opening %s (attempt %d)", t.filename, attempt) var err error t.file, err = os.Open(t.filename) if err == nil { // TODO: not interested in old events? // t.file.Seek(0, os.SEEK_END) t.reader = bufio.NewReader(t.file) return nil } lastErr = err glog.V(4).Infof("open log file %s error: %v", t.filename, err) if interval >= maxRetryInterval { break } select { case <-time.After(interval): case <-t.stop: t.readerErr = io.EOF return fmt.Errorf("watch was cancelled") } } err := fmt.Errorf("can't open log file %s: %v", t.filename, lastErr) t.readerErr = err return err } func (t *Tail) watchLoop() { for { err := t.watchFile() if err != nil { glog.Errorf("Tail failed on %s: %v", t.filename, err) break } } } func (t *Tail) watchFile() error { err := t.attemptOpen() if err != nil { return err } defer t.file.Close() watchDir := filepath.Dir(t.filename) err = t.watcher.AddWatch(watchDir, inotify.IN_MOVED_FROM|inotify.IN_DELETE) if err != nil { return fmt.Errorf("Failed to add watch to directory %s: %v", watchDir, err) } defer t.watcher.RemoveWatch(watchDir) for { select { case event := <-t.watcher.Event: eventPath := filepath.Clean(event.Name) // Directory events have an extra '/' if eventPath == t.filename { glog.V(4).Infof("Log file %s moved/deleted", t.filename) return nil } case <-t.stop: return fmt.Errorf("watch was cancelled") } } } cadvisor-0.27.1/utils/tail/tail_test.go000066400000000000000000000017671315410276000200350ustar00rootroot00000000000000// Copyright 2016 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 tail import ( "io" "testing" "github.com/stretchr/testify/assert" ) func TestReadNewTail(t *testing.T) { // Read should return (0, io.EOF) before first // attemptOpen. tail, err := newTail("test/nonexist/file") assert.NoError(t, err) buf := make([]byte, 0, 100) n, err := tail.Read(buf) assert.Equal(t, n, 0) assert.Equal(t, len(buf), 0) assert.EqualError(t, err, io.EOF.Error()) } cadvisor-0.27.1/utils/timed_store.go000066400000000000000000000116741315410276000174300ustar00rootroot00000000000000// 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 utils import ( "sort" "time" ) type timedStoreDataSlice []timedStoreData func (t timedStoreDataSlice) Less(i, j int) bool { return t[i].timestamp.Before(t[j].timestamp) } func (t timedStoreDataSlice) Len() int { return len(t) } func (t timedStoreDataSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } // A time-based buffer for ContainerStats. // Holds information for a specific time period and/or a max number of items. type TimedStore struct { buffer timedStoreDataSlice age time.Duration maxItems int } type timedStoreData struct { timestamp time.Time data interface{} } // Returns a new thread-compatible TimedStore. // A maxItems value of -1 means no limit. func NewTimedStore(age time.Duration, maxItems int) *TimedStore { return &TimedStore{ buffer: make(timedStoreDataSlice, 0), age: age, maxItems: maxItems, } } // Adds an element to the start of the buffer (removing one from the end if necessary). func (self *TimedStore) Add(timestamp time.Time, item interface{}) { data := timedStoreData{ timestamp: timestamp, data: item, } // Common case: data is added in order. if len(self.buffer) == 0 || !timestamp.Before(self.buffer[len(self.buffer)-1].timestamp) { self.buffer = append(self.buffer, data) } else { // Data is out of order; insert it in the correct position. index := sort.Search(len(self.buffer), func(index int) bool { return self.buffer[index].timestamp.After(timestamp) }) self.buffer = append(self.buffer, timedStoreData{}) // Make room to shift the elements copy(self.buffer[index+1:], self.buffer[index:]) // Shift the elements over self.buffer[index] = data } // Remove any elements before eviction time. // TODO(rjnagal): This is assuming that the added entry has timestamp close to now. evictTime := timestamp.Add(-self.age) index := sort.Search(len(self.buffer), func(index int) bool { return self.buffer[index].timestamp.After(evictTime) }) if index < len(self.buffer) { self.buffer = self.buffer[index:] } // Remove any elements if over our max size. if self.maxItems >= 0 && len(self.buffer) > self.maxItems { startIndex := len(self.buffer) - self.maxItems self.buffer = self.buffer[startIndex:] } } // Returns up to maxResult elements in the specified time period (inclusive). // Results are from first to last. maxResults of -1 means no limit. func (self *TimedStore) InTimeRange(start, end time.Time, maxResults int) []interface{} { // No stats, return empty. if len(self.buffer) == 0 { return []interface{}{} } var startIndex int if start.IsZero() { // None specified, start at the beginning. startIndex = len(self.buffer) - 1 } else { // Start is the index before the elements smaller than it. We do this by // finding the first element smaller than start and taking the index // before that element startIndex = sort.Search(len(self.buffer), func(index int) bool { // buffer[index] < start return self.getData(index).timestamp.Before(start) }) - 1 // Check if start is after all the data we have. if startIndex < 0 { return []interface{}{} } } var endIndex int if end.IsZero() { // None specified, end with the latest stats. endIndex = 0 } else { // End is the first index smaller than or equal to it (so, not larger). endIndex = sort.Search(len(self.buffer), func(index int) bool { // buffer[index] <= t -> !(buffer[index] > t) return !self.getData(index).timestamp.After(end) }) // Check if end is before all the data we have. if endIndex == len(self.buffer) { return []interface{}{} } } // Trim to maxResults size. numResults := startIndex - endIndex + 1 if maxResults != -1 && numResults > maxResults { startIndex -= numResults - maxResults numResults = maxResults } // Return in sorted timestamp order so from the "back" to "front". result := make([]interface{}, numResults) for i := 0; i < numResults; i++ { result[i] = self.Get(startIndex - i) } return result } // Gets the element at the specified index. Note that elements are output in LIFO order. func (self *TimedStore) Get(index int) interface{} { return self.getData(index).data } // Gets the data at the specified index. Note that elements are output in LIFO order. func (self *TimedStore) getData(index int) timedStoreData { return self.buffer[len(self.buffer)-index-1] } func (self *TimedStore) Size() int { return len(self.buffer) } cadvisor-0.27.1/utils/timed_store_test.go000066400000000000000000000164521315410276000204660ustar00rootroot00000000000000// 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 utils import ( "testing" "time" "github.com/stretchr/testify/assert" ) func createTime(id int) time.Time { var zero time.Time return zero.Add(time.Duration(id+1) * time.Second) } func expectSize(t *testing.T, sb *TimedStore, expectedSize int) { if sb.Size() != expectedSize { t.Errorf("Expected size %v, got %v", expectedSize, sb.Size()) } } func expectAllElements(t *testing.T, sb *TimedStore, expected []int) { size := sb.Size() els := make([]interface{}, size) for i := 0; i < size; i++ { els[i] = sb.Get(size - i - 1) } expectElements(t, []interface{}(els), expected) } func expectElements(t *testing.T, actual []interface{}, expected []int) { if len(actual) != len(expected) { t.Errorf("Expected elements %v, got %v", expected, actual) return } for i, el := range actual { if el.(int) != expected[i] { t.Errorf("Expected elements %v, got %v", expected, actual) return } } } func TestAdd(t *testing.T) { sb := NewTimedStore(5*time.Second, 100) // Add 1. sb.Add(createTime(0), 0) expectSize(t, sb, 1) expectAllElements(t, sb, []int{0}) // Fill the buffer. for i := 1; i <= 5; i++ { expectSize(t, sb, i) sb.Add(createTime(i), i) } expectSize(t, sb, 5) expectAllElements(t, sb, []int{1, 2, 3, 4, 5}) // Add more than is available in the buffer sb.Add(createTime(6), 6) expectSize(t, sb, 5) expectAllElements(t, sb, []int{2, 3, 4, 5, 6}) // Replace all elements. for i := 7; i <= 10; i++ { sb.Add(createTime(i), i) } expectSize(t, sb, 5) expectAllElements(t, sb, []int{6, 7, 8, 9, 10}) } func TestGet(t *testing.T) { sb := NewTimedStore(5*time.Second, -1) sb.Add(createTime(1), 1) sb.Add(createTime(2), 2) sb.Add(createTime(3), 3) expectSize(t, sb, 3) assert := assert.New(t) assert.Equal(sb.Get(0).(int), 3) assert.Equal(sb.Get(1).(int), 2) assert.Equal(sb.Get(2).(int), 1) } func TestInTimeRange(t *testing.T) { sb := NewTimedStore(5*time.Second, -1) assert := assert.New(t) var empty time.Time // No elements. assert.Empty(sb.InTimeRange(createTime(0), createTime(5), 10)) assert.Empty(sb.InTimeRange(createTime(0), empty, 10)) assert.Empty(sb.InTimeRange(empty, createTime(5), 10)) assert.Empty(sb.InTimeRange(empty, empty, 10)) // One element. sb.Add(createTime(1), 1) expectSize(t, sb, 1) expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int{1}) expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 10), []int{1}) expectElements(t, sb.InTimeRange(createTime(0), createTime(1), 10), []int{1}) expectElements(t, sb.InTimeRange(createTime(1), createTime(1), 10), []int{1}) assert.Empty(sb.InTimeRange(createTime(2), createTime(5), 10)) // Two element. sb.Add(createTime(2), 2) expectSize(t, sb, 2) expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int{1, 2}) expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 10), []int{1, 2}) expectElements(t, sb.InTimeRange(createTime(0), createTime(2), 10), []int{1, 2}) expectElements(t, sb.InTimeRange(createTime(1), createTime(2), 10), []int{1, 2}) expectElements(t, sb.InTimeRange(createTime(1), createTime(1), 10), []int{1}) expectElements(t, sb.InTimeRange(createTime(2), createTime(2), 10), []int{2}) assert.Empty(sb.InTimeRange(createTime(3), createTime(5), 10)) // Many elements. sb.Add(createTime(3), 3) sb.Add(createTime(4), 4) expectSize(t, sb, 4) expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 10), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(createTime(0), createTime(4), 10), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(createTime(1), createTime(4), 10), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(createTime(0), createTime(2), 10), []int{1, 2}) expectElements(t, sb.InTimeRange(createTime(1), createTime(2), 10), []int{1, 2}) expectElements(t, sb.InTimeRange(createTime(2), createTime(3), 10), []int{2, 3}) expectElements(t, sb.InTimeRange(createTime(3), createTime(4), 10), []int{3, 4}) expectElements(t, sb.InTimeRange(createTime(3), createTime(5), 10), []int{3, 4}) assert.Empty(sb.InTimeRange(createTime(5), createTime(5), 10)) // Start and end time does't ignore maxResults. expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 1), []int{4}) // No start time. expectElements(t, sb.InTimeRange(empty, createTime(5), 10), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(empty, createTime(4), 10), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(empty, createTime(3), 10), []int{1, 2, 3}) expectElements(t, sb.InTimeRange(empty, createTime(2), 10), []int{1, 2}) expectElements(t, sb.InTimeRange(empty, createTime(1), 10), []int{1}) // No end time. expectElements(t, sb.InTimeRange(createTime(0), empty, 10), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(createTime(1), empty, 10), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(createTime(2), empty, 10), []int{2, 3, 4}) expectElements(t, sb.InTimeRange(createTime(3), empty, 10), []int{3, 4}) expectElements(t, sb.InTimeRange(createTime(4), empty, 10), []int{4}) // No start or end time. expectElements(t, sb.InTimeRange(empty, empty, 10), []int{1, 2, 3, 4}) // Start after data. assert.Empty(sb.InTimeRange(createTime(5), createTime(5), 10)) assert.Empty(sb.InTimeRange(createTime(5), empty, 10)) // End before data. assert.Empty(sb.InTimeRange(createTime(0), createTime(0), 10)) assert.Empty(sb.InTimeRange(empty, createTime(0), 10)) } func TestInTimeRangeWithLimit(t *testing.T) { sb := NewTimedStore(5*time.Second, -1) sb.Add(createTime(1), 1) sb.Add(createTime(2), 2) sb.Add(createTime(3), 3) sb.Add(createTime(4), 4) expectSize(t, sb, 4) var empty time.Time // Limit cuts off from latest timestamp. expectElements(t, sb.InTimeRange(empty, empty, 4), []int{1, 2, 3, 4}) expectElements(t, sb.InTimeRange(empty, empty, 3), []int{2, 3, 4}) expectElements(t, sb.InTimeRange(empty, empty, 2), []int{3, 4}) expectElements(t, sb.InTimeRange(empty, empty, 1), []int{4}) assert.Empty(t, sb.InTimeRange(empty, empty, 0)) } func TestLimitedSize(t *testing.T) { sb := NewTimedStore(time.Hour, 5) // Add 1. sb.Add(createTime(0), 0) expectSize(t, sb, 1) expectAllElements(t, sb, []int{0}) // Fill the buffer. for i := 1; i <= 5; i++ { expectSize(t, sb, i) sb.Add(createTime(i), i) } expectSize(t, sb, 5) expectAllElements(t, sb, []int{1, 2, 3, 4, 5}) // Add more than is available in the buffer sb.Add(createTime(6), 6) expectSize(t, sb, 5) expectAllElements(t, sb, []int{2, 3, 4, 5, 6}) // Replace all elements. for i := 7; i <= 10; i++ { sb.Add(createTime(i), i) } expectSize(t, sb, 5) expectAllElements(t, sb, []int{6, 7, 8, 9, 10}) } cadvisor-0.27.1/utils/utils.go000066400000000000000000000015661315410276000162510ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package utils import "fmt" // Returns a mask of all cores on the machine if the passed-in mask is empty. func FixCpuMask(mask string, cores int) string { if mask == "" { if cores > 1 { mask = fmt.Sprintf("0-%d", cores-1) } else { mask = "0" } } return mask } cadvisor-0.27.1/validate/000077500000000000000000000000001315410276000152035ustar00rootroot00000000000000cadvisor-0.27.1/validate/validate.go000066400000000000000000000223731315410276000173320ustar00rootroot00000000000000// 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 /validate content. // Validates cadvisor dependencies - kernel, os, docker setup. package validate import ( "fmt" "io/ioutil" "log" "net/http" "path" "strings" "github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/manager" "github.com/google/cadvisor/utils" "github.com/opencontainers/runc/libcontainer/cgroups" ) const ( ValidatePage = "/validate/" Supported = "[Supported, but not recommended]" Unsupported = "[Unsupported]" Recommended = "[Supported and recommended]" Unknown = "[Unknown]" VersionFormat = "%d.%d%s" OutputFormat = "%s: %s\n\t%s\n\n" ) func getMajorMinor(version string) (int, int, error) { var major, minor int var ign string n, err := fmt.Sscanf(version, VersionFormat, &major, &minor, &ign) if n != 3 || err != nil { log.Printf("Failed to parse version for %s", version) return -1, -1, err } return major, minor, nil } func validateKernelVersion(version string) (string, string) { desc := fmt.Sprintf("Kernel version is %s. Versions >= 2.6 are supported. 3.0+ are recommended.\n", version) major, minor, err := getMajorMinor(version) if err != nil { desc = fmt.Sprintf("Could not parse kernel version. %s", desc) return Unknown, desc } if major < 2 { return Unsupported, desc } if major == 2 && minor < 6 { return Unsupported, desc } if major >= 3 { return Recommended, desc } return Supported, desc } func validateDockerVersion(version string) (string, string) { desc := fmt.Sprintf("Docker version is %s. Versions >= 1.0 are supported. 1.2+ are recommended.\n", version) major, minor, err := getMajorMinor(version) if err != nil { desc = fmt.Sprintf("Could not parse docker version. %s\n\t", desc) return Unknown, desc } if major < 1 { return Unsupported, desc } if major == 1 && minor < 2 { return Supported, desc } return Recommended, desc } func getEnabledCgroups() (map[string]int, error) { out, err := ioutil.ReadFile("/proc/cgroups") if err != nil { return nil, err } cgroups := make(map[string]int) for i, line := range strings.Split(string(out), "\n") { var cgroup string var ign, enabled int if i == 0 || line == "" { continue } n, err := fmt.Sscanf(line, "%s %d %d %d", &cgroup, &ign, &ign, &enabled) if n != 4 || err != nil { if err == nil { err = fmt.Errorf("failed to parse /proc/cgroup entry %s", line) } return nil, err } cgroups[cgroup] = enabled } return cgroups, nil } func areCgroupsPresent(available map[string]int, desired []string) (bool, string) { for _, cgroup := range desired { enabled, ok := available[cgroup] if !ok { reason := fmt.Sprintf("Missing cgroup %s. Available cgroups: %v\n", cgroup, available) return false, reason } if enabled != 1 { reason := fmt.Sprintf("Cgroup %s not enabled. Available cgroups: %v\n", cgroup, available) return false, reason } } return true, "" } func validateMemoryAccounting(available_cgroups map[string]int) string { ok, _ := areCgroupsPresent(available_cgroups, []string{"memory"}) if !ok { return "\tHierarchical memory accounting status unknown: memory cgroup not enabled.\n" } mnt, err := cgroups.FindCgroupMountpoint("memory") if err != nil { return "\tHierarchical memory accounting status unknown: memory cgroup not mounted.\n" } hier, err := ioutil.ReadFile(path.Join(mnt, "memory.use_hierarchy")) if err != nil { return "\tHierarchical memory accounting status unknown: hierarchy interface unavailable.\n" } var enabled int n, err := fmt.Sscanf(string(hier), "%d", &enabled) if err != nil || n != 1 { return "\tHierarchical memory accounting status unknown: hierarchy interface unreadable.\n" } if enabled == 1 { return "\tHierarchical memory accounting enabled. Reported memory usage includes memory used by child containers.\n" } return "\tHierarchical memory accounting disabled. Memory usage does not include usage from child containers.\n" } func validateCgroups() (string, string) { required_cgroups := []string{"cpu", "cpuacct"} recommended_cgroups := []string{"memory", "blkio", "cpuset", "devices", "freezer"} available_cgroups, err := getEnabledCgroups() desc := fmt.Sprintf("\tFollowing cgroups are required: %v\n\tFollowing other cgroups are recommended: %v\n", required_cgroups, recommended_cgroups) if err != nil { desc = fmt.Sprintf("Could not parse /proc/cgroups.\n%s", desc) return Unknown, desc } ok, out := areCgroupsPresent(available_cgroups, required_cgroups) if !ok { out += desc return Unsupported, out } ok, out = areCgroupsPresent(available_cgroups, recommended_cgroups) if !ok { // supported, but not recommended. out += desc return Supported, out } out = fmt.Sprintf("Available cgroups: %v\n", available_cgroups) out += desc out += validateMemoryAccounting(available_cgroups) return Recommended, out } func validateDockerInfo() (string, string) { info, err := docker.ValidateInfo() if err != nil { return Unsupported, fmt.Sprintf("Docker setup is invalid: %v", err) } desc := fmt.Sprintf("Docker exec driver is %s. Storage driver is %s.\n", info.ExecutionDriver, info.Driver) return Recommended, desc } func validateCgroupMounts() (string, string) { const recommendedMount = "/sys/fs/cgroup" desc := fmt.Sprintf("\tAny cgroup mount point that is detectible and accessible is supported. %s is recommended as a standard location.\n", recommendedMount) mnt, err := cgroups.FindCgroupMountpoint("cpu") if err != nil { out := "Could not locate cgroup mount point.\n" out += desc return Unknown, out } mnt = path.Dir(mnt) if !utils.FileExists(mnt) { out := fmt.Sprintf("Cgroup mount directory %s inaccessible.\n", mnt) out += desc return Unsupported, out } mounts, err := ioutil.ReadDir(mnt) if err != nil { out := fmt.Sprintf("Could not read cgroup mount directory %s.\n", mnt) out += desc return Unsupported, out } mountNames := "\tCgroup mount directories: " for _, mount := range mounts { mountNames += mount.Name() + " " } mountNames += "\n" out := fmt.Sprintf("Cgroups are mounted at %s.\n", mnt) out += mountNames out += desc info, err := ioutil.ReadFile("/proc/mounts") if err != nil { out := fmt.Sprintf("Could not read /proc/mounts.\n") out += desc return Unsupported, out } out += "\tCgroup mounts:\n" for _, line := range strings.Split(string(info), "\n") { if strings.Contains(line, " cgroup ") { out += "\t" + line + "\n" } } if mnt == recommendedMount { return Recommended, out } return Supported, out } func validateIoScheduler(containerManager manager.Manager) (string, string) { var desc string mi, err := containerManager.GetMachineInfo() if err != nil { return Unknown, "Machine info not available\n\t" } cfq := false for _, disk := range mi.DiskMap { desc += fmt.Sprintf("\t Disk %q Scheduler type %q.\n", disk.Name, disk.Scheduler) if disk.Scheduler == "cfq" { cfq = true } } // Since we get lot of random block devices, report recommended if // at least one of them is on cfq. Report Supported otherwise. if cfq { desc = "At least one device supports 'cfq' I/O scheduler. Some disk stats can be reported.\n" + desc return Recommended, desc } desc = "None of the devices support 'cfq' I/O scheduler. No disk stats can be reported.\n" + desc return Supported, desc } func HandleRequest(w http.ResponseWriter, containerManager manager.Manager) error { // Get cAdvisor version Info. versionInfo, err := containerManager.GetVersionInfo() if err != nil { return err } out := fmt.Sprintf("cAdvisor version: %s\n\n", versionInfo.CadvisorVersion) // No OS is preferred or unsupported as of now. out += fmt.Sprintf("OS version: %s\n\n", versionInfo.ContainerOsVersion) kernelValidation, desc := validateKernelVersion(versionInfo.KernelVersion) out += fmt.Sprintf(OutputFormat, "Kernel version", kernelValidation, desc) cgroupValidation, desc := validateCgroups() out += fmt.Sprintf(OutputFormat, "Cgroup setup", cgroupValidation, desc) mountsValidation, desc := validateCgroupMounts() out += fmt.Sprintf(OutputFormat, "Cgroup mount setup", mountsValidation, desc) dockerValidation, desc := validateDockerVersion(versionInfo.DockerVersion) out += fmt.Sprintf(OutputFormat, "Docker version", dockerValidation, desc) dockerInfoValidation, desc := validateDockerInfo() out += fmt.Sprintf(OutputFormat, "Docker driver setup", dockerInfoValidation, desc) ioSchedulerValidation, desc := validateIoScheduler(containerManager) out += fmt.Sprintf(OutputFormat, "Block device setup", ioSchedulerValidation, desc) // Output debug info. debugInfo := containerManager.DebugInfo() for category, lines := range debugInfo { out += fmt.Sprintf(OutputFormat, category, "", strings.Join(lines, "\n\t")) } _, err = w.Write([]byte(out)) return err } cadvisor-0.27.1/validate/validate_test.go000066400000000000000000000076721315410276000203760ustar00rootroot00000000000000// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package validate import ( "fmt" "strings" "testing" ) var ( standardDesc = "Versions >= %s are supported. %s+ are recommended" dockerStandardDesc = fmt.Sprintf(standardDesc, "1.0", "1.2") kernelStandardDesc = fmt.Sprintf(standardDesc, "2.6", "3.0") dockerErrorDesc = "Could not parse docker version" kernelErrorDesc = "Could not parse kernel version" ) func TestGetMajorMinor(t *testing.T) { cases := []struct { version string major int minor int err error }{ {"0.1beta", 0, 1, nil}, {"0.1.2", 0, 1, nil}, {"-1.-1beta", -1, -1, nil}, {"0.1", -1, -1, fmt.Errorf("have error")}, {"0", -1, -1, fmt.Errorf("have error")}, {"beta", -1, -1, fmt.Errorf("have error")}, } for i, c := range cases { ma, mi, e := getMajorMinor(c.version) if (e != nil) != (c.err != nil) { t.Errorf("[%d] Unexpected err, should %v, but got %v", i, c.err, e) } if ma != c.major { t.Errorf("[%d] Unexpected major, should %v, but got %v", i, c.major, ma) } if mi != c.minor { t.Errorf("[%d] Unexpected minor, should %v, but got %v", i, c.minor, mi) } } } func TestValidateKernelVersion(t *testing.T) { cases := []struct { version string result string desc string }{ {"2.6.3", Supported, kernelStandardDesc}, {"3.6.3", Recommended, kernelStandardDesc}, {"1.0beta", Unsupported, kernelStandardDesc}, {"0.1beta", Unsupported, kernelStandardDesc}, {"0.1", Unknown, kernelErrorDesc}, {"3.1", Unknown, kernelErrorDesc}, } for i, c := range cases { res, desc := validateKernelVersion(c.version) if res != c.result { t.Errorf("[%d] Unexpected result, should %v, but got %v", i, c.result, res) } if !strings.Contains(desc, c.desc) { t.Errorf("[%d] Unexpected description, should %v, but got %v", i, c.desc, desc) } } } func TestValidateDockerVersion(t *testing.T) { cases := []struct { version string result string desc string }{ {"1.1.3", Supported, dockerStandardDesc}, {"1.6.3", Recommended, dockerStandardDesc}, {"1.0beta", Supported, dockerStandardDesc}, {"0.1beta", Unsupported, dockerStandardDesc}, {"0.1", Unknown, dockerErrorDesc}, {"1.6", Unknown, dockerErrorDesc}, } for i, c := range cases { res, desc := validateDockerVersion(c.version) if res != c.result { t.Errorf("[%d] Unexpected result, should %v, but got %v", i, c.result, res) } if !strings.Contains(desc, c.desc) { t.Errorf("[%d] Unexpected description, should %v, but got %v", i, c.desc, desc) } } } func TestAreCgroupsPresent(t *testing.T) { cases := []struct { available map[string]int desired []string result bool reason string }{ {map[string]int{"memory": 1}, []string{"memory"}, true, ""}, {map[string]int{"memory": 2}, []string{"memory"}, false, "memory not enabled. Available cgroups"}, {map[string]int{"memory": 0}, []string{"memory"}, false, "memory not enabled. Available cgroups"}, {map[string]int{"memory": 1}, []string{"blkio"}, false, "Missing cgroup blkio. Available cgroups"}, } for i, c := range cases { result, reason := areCgroupsPresent(c.available, c.desired) if result != c.result { t.Errorf("[%d] Unexpected result, should %v, but got %v", i, c.result, result) } if (c.reason == "" && reason != "") || (c.reason != "" && !strings.Contains(reason, c.reason)) { t.Errorf("[%d] Unexpected result, should %v, but got %v", i, c.reason, reason) } } } cadvisor-0.27.1/vendor/000077500000000000000000000000001315410276000147075ustar00rootroot00000000000000cadvisor-0.27.1/vendor/cloud.google.com/000077500000000000000000000000001315410276000200455ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/000077500000000000000000000000001315410276000167465ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/Microsoft/000077500000000000000000000000001315410276000207135ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/SeanDolphin/000077500000000000000000000000001315410276000211525ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/Shopify/000077500000000000000000000000001315410276000203675ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/abbot/000077500000000000000000000000001315410276000200355ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/aws/000077500000000000000000000000001315410276000175405ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/beorn7/000077500000000000000000000000001315410276000201425ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/blang/000077500000000000000000000000001315410276000200315ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/coreos/000077500000000000000000000000001315410276000202405ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/docker/000077500000000000000000000000001315410276000202155ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/eapache/000077500000000000000000000000001315410276000203345ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/euank/000077500000000000000000000000001315410276000200515ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/euank/go-kmsg-parser/000077500000000000000000000000001315410276000227075ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/euank/go-kmsg-parser/LICENSE000066400000000000000000000261351315410276000237230ustar00rootroot00000000000000 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 APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. cadvisor-0.27.1/vendor/github.com/euank/go-kmsg-parser/kmsgparser/000077500000000000000000000000001315410276000250655ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/euank/go-kmsg-parser/kmsgparser/kmsgparser.go000066400000000000000000000126171315410276000276010ustar00rootroot00000000000000/* Copyright 2016 Euan Kemp 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 kmsgparser implements a parser for the Linux `/dev/kmsg` format. // More information about this format may be found here: // https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg // Some parts of it are slightly inspired by rsyslog's contrib module: // https://github.com/rsyslog/rsyslog/blob/v8.22.0/contrib/imkmsg/kmsg.c package kmsgparser import ( "fmt" "io" "os" "strconv" "strings" "syscall" "time" ) // Parser is a parser for the kernel ring buffer found at /dev/kmsg type Parser interface { // SeekEnd moves the parser to the end of the kmsg queue. SeekEnd() error // Parse provides a channel of messages read from the kernel ring buffer. // When first called, it will read the existing ringbuffer, after which it will emit new messages as they occur. Parse() <-chan Message // SetLogger sets the logger that will be used to report malformed kernel // ringbuffer lines or unexpected kmsg read errors. SetLogger(Logger) // Close closes the underlying kmsg reader for this parser Close() error } // Message represents a given kmsg logline, including its timestamp (as // calculated based on offset from boot time), its possibly multi-line body, // and so on. More information about these mssages may be found here: // https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg type Message struct { Priority int SequenceNumber int Timestamp time.Time Message string } func NewParser() (Parser, error) { f, err := os.Open("/dev/kmsg") if err != nil { return nil, err } bootTime, err := getBootTime() if err != nil { return nil, err } return &parser{ log: &StandardLogger{nil}, kmsgReader: f, bootTime: bootTime, }, nil } type ReadSeekCloser interface { io.ReadCloser io.Seeker } type parser struct { log Logger kmsgReader ReadSeekCloser bootTime time.Time } func getBootTime() (time.Time, error) { var sysinfo syscall.Sysinfo_t err := syscall.Sysinfo(&sysinfo) if err != nil { return time.Time{}, fmt.Errorf("could not get boot time: %v", err) } // sysinfo only has seconds return time.Now().Add(-1 * (time.Duration(sysinfo.Uptime) * time.Second)), nil } func (p *parser) SetLogger(log Logger) { p.log = log } func (p *parser) Close() error { return p.kmsgReader.Close() } func (p *parser) SeekEnd() error { _, err := p.kmsgReader.Seek(0, os.SEEK_END) return err } // Parse will read from the provided reader and provide a channel of messages // parsed. // If the provided reader *is not* a proper Linux kmsg device, Parse might not // behave correctly since it relies on specific behavior of `/dev/kmsg` // // A goroutine is created to process the provided reader. The goroutine will // exit when the given reader is closed. // Closing the passed in reader will cause the goroutine to exit. func (p *parser) Parse() <-chan Message { output := make(chan Message, 1) go func() { defer close(output) msg := make([]byte, 8192) for { // Each read call gives us one full message. // https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg n, err := p.kmsgReader.Read(msg) if err != nil { if err == syscall.EPIPE { p.log.Warningf("short read from kmsg; skipping") continue } if err == io.EOF { p.log.Infof("kmsg reader closed, shutting down") return } p.log.Errorf("error reading /dev/kmsg: %v", err) return } msgStr := string(msg[:n]) message, err := p.parseMessage(msgStr) if err != nil { p.log.Warningf("unable to parse kmsg message %q: %v", msgStr, err) continue } output <- message } }() return output } func (p *parser) parseMessage(input string) (Message, error) { // Format: // PRIORITY,SEQUENCE_NUM,TIMESTAMP,-;MESSAGE parts := strings.SplitN(input, ";", 2) if len(parts) != 2 { return Message{}, fmt.Errorf("invalid kmsg; must contain a ';'") } metadata, message := parts[0], parts[1] metadataParts := strings.Split(metadata, ",") if len(metadataParts) < 3 { return Message{}, fmt.Errorf("invalid kmsg: must contain at least 3 ',' separated pieces at the start") } priority, sequence, timestamp := metadataParts[0], metadataParts[1], metadataParts[2] prioNum, err := strconv.Atoi(priority) if err != nil { return Message{}, fmt.Errorf("could not parse %q as priority: %v", priority, err) } sequenceNum, err := strconv.Atoi(sequence) if err != nil { return Message{}, fmt.Errorf("could not parse %q as sequence number: %v", priority, err) } timestampUsFromBoot, err := strconv.ParseInt(timestamp, 10, 64) if err != nil { return Message{}, fmt.Errorf("could not parse %q as timestamp: %v", priority, err) } // timestamp is offset in microsecond from boottime. msgTime := p.bootTime.Add(time.Duration(timestampUsFromBoot) * time.Microsecond) return Message{ Priority: prioNum, SequenceNumber: sequenceNum, Timestamp: msgTime, Message: message, }, nil } cadvisor-0.27.1/vendor/github.com/euank/go-kmsg-parser/kmsgparser/log.go000066400000000000000000000027201315410276000261760ustar00rootroot00000000000000/* Copyright 2016 Euan Kemp 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 kmsgparser import stdlog "log" // Logger is a glog compatible logging interface // The StandardLogger struct can be used to wrap a log.Logger from the golang // "log" package to create a standard a logger fulfilling this interface as // well. type Logger interface { Warningf(string, ...interface{}) Infof(string, ...interface{}) Errorf(string, ...interface{}) } // StandardLogger adapts the "log" package's Logger interface to be a Logger type StandardLogger struct { *stdlog.Logger } func (s *StandardLogger) Warningf(fmt string, args ...interface{}) { if s.Logger == nil { return } s.Logger.Printf("[WARNING] "+fmt, args) } func (s *StandardLogger) Infof(fmt string, args ...interface{}) { if s.Logger == nil { return } s.Logger.Printf("[INFO] "+fmt, args) } func (s *StandardLogger) Errorf(fmt string, args ...interface{}) { if s.Logger == nil { return } s.Logger.Printf("[INFO] "+fmt, args) } cadvisor-0.27.1/vendor/github.com/garyburd/000077500000000000000000000000001315410276000205655ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/go-ini/000077500000000000000000000000001315410276000201305ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/godbus/000077500000000000000000000000001315410276000202315ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/golang/000077500000000000000000000000001315410276000202155ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/influxdb/000077500000000000000000000000001315410276000205615ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/jmespath/000077500000000000000000000000001315410276000205615ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/klauspost/000077500000000000000000000000001315410276000207735ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/kr/000077500000000000000000000000001315410276000173625ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/matttproud/000077500000000000000000000000001315410276000211515ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/mrunalp/000077500000000000000000000000001315410276000204245ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/000077500000000000000000000000001315410276000217755ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/000077500000000000000000000000001315410276000227445ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/LICENSE000066400000000000000000000250061315410276000237540ustar00rootroot00000000000000 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 Copyright 2014 Docker, Inc. 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. cadvisor-0.27.1/vendor/github.com/opencontainers/runc/NOTICE000066400000000000000000000010061315410276000236450ustar00rootroot00000000000000runc Copyright 2012-2015 Docker, Inc. This product includes software developed at Docker, Inc. (http://www.docker.com). The following is courtesy of our legal counsel: Use and transfer of Docker may be subject to certain restrictions by the United States and other governments. It is your responsibility to ensure that your use and/or transfer does not violate applicable laws. For more information, please see http://www.bis.doc.gov See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/000077500000000000000000000000001315410276000254155ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/README.md000066400000000000000000000225571315410276000267070ustar00rootroot00000000000000# libcontainer [![GoDoc](https://godoc.org/github.com/opencontainers/runc/libcontainer?status.svg)](https://godoc.org/github.com/opencontainers/runc/libcontainer) Libcontainer provides a native Go implementation for creating containers with namespaces, cgroups, capabilities, and filesystem access controls. It allows you to manage the lifecycle of the container performing additional operations after the container is created. #### Container A container is a self contained execution environment that shares the kernel of the host system and which is (optionally) isolated from other containers in the system. #### Using libcontainer Because containers are spawned in a two step process you will need a binary that will be executed as the init process for the container. In libcontainer, we use the current binary (/proc/self/exe) to be executed as the init process, and use arg "init", we call the first step process "bootstrap", so you always need a "init" function as the entry of "bootstrap". In addition to the go init function the early stage bootstrap is handled by importing [nsenter](https://github.com/opencontainers/runc/blob/master/libcontainer/nsenter/README.md). ```go import ( _ "github.com/opencontainers/runc/libcontainer/nsenter" ) func init() { if len(os.Args) > 1 && os.Args[1] == "init" { runtime.GOMAXPROCS(1) runtime.LockOSThread() factory, _ := libcontainer.New("") if err := factory.StartInitialization(); err != nil { logrus.Fatal(err) } panic("--this line should have never been executed, congratulations--") } } ``` Then to create a container you first have to initialize an instance of a factory that will handle the creation and initialization for a container. ```go factory, err := libcontainer.New("/var/lib/container", libcontainer.Cgroupfs, libcontainer.InitArgs(os.Args[0], "init")) if err != nil { logrus.Fatal(err) return } ``` Once you have an instance of the factory created we can create a configuration struct describing how the container is to be created. A sample would look similar to this: ```go defaultMountFlags := unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV config := &configs.Config{ Rootfs: "/your/path/to/rootfs", Capabilities: &configs.Capabilities{ Bounding: []string{ "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER", "CAP_MKNOD", "CAP_NET_RAW", "CAP_SETGID", "CAP_SETUID", "CAP_SETFCAP", "CAP_SETPCAP", "CAP_NET_BIND_SERVICE", "CAP_SYS_CHROOT", "CAP_KILL", "CAP_AUDIT_WRITE", }, Effective: []string{ "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER", "CAP_MKNOD", "CAP_NET_RAW", "CAP_SETGID", "CAP_SETUID", "CAP_SETFCAP", "CAP_SETPCAP", "CAP_NET_BIND_SERVICE", "CAP_SYS_CHROOT", "CAP_KILL", "CAP_AUDIT_WRITE", }, Inheritable: []string{ "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER", "CAP_MKNOD", "CAP_NET_RAW", "CAP_SETGID", "CAP_SETUID", "CAP_SETFCAP", "CAP_SETPCAP", "CAP_NET_BIND_SERVICE", "CAP_SYS_CHROOT", "CAP_KILL", "CAP_AUDIT_WRITE", }, Permitted: []string{ "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER", "CAP_MKNOD", "CAP_NET_RAW", "CAP_SETGID", "CAP_SETUID", "CAP_SETFCAP", "CAP_SETPCAP", "CAP_NET_BIND_SERVICE", "CAP_SYS_CHROOT", "CAP_KILL", "CAP_AUDIT_WRITE", }, Ambient: []string{ "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER", "CAP_MKNOD", "CAP_NET_RAW", "CAP_SETGID", "CAP_SETUID", "CAP_SETFCAP", "CAP_SETPCAP", "CAP_NET_BIND_SERVICE", "CAP_SYS_CHROOT", "CAP_KILL", "CAP_AUDIT_WRITE", }, }, Namespaces: configs.Namespaces([]configs.Namespace{ {Type: configs.NEWNS}, {Type: configs.NEWUTS}, {Type: configs.NEWIPC}, {Type: configs.NEWPID}, {Type: configs.NEWUSER}, {Type: configs.NEWNET}, }), Cgroups: &configs.Cgroup{ Name: "test-container", Parent: "system", Resources: &configs.Resources{ MemorySwappiness: nil, AllowAllDevices: nil, AllowedDevices: configs.DefaultAllowedDevices, }, }, MaskPaths: []string{ "/proc/kcore", "/sys/firmware", }, ReadonlyPaths: []string{ "/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus", }, Devices: configs.DefaultAutoCreatedDevices, Hostname: "testing", Mounts: []*configs.Mount{ { Source: "proc", Destination: "/proc", Device: "proc", Flags: defaultMountFlags, }, { Source: "tmpfs", Destination: "/dev", Device: "tmpfs", Flags: unix.MS_NOSUID | unix.MS_STRICTATIME, Data: "mode=755", }, { Source: "devpts", Destination: "/dev/pts", Device: "devpts", Flags: unix.MS_NOSUID | unix.MS_NOEXEC, Data: "newinstance,ptmxmode=0666,mode=0620,gid=5", }, { Device: "tmpfs", Source: "shm", Destination: "/dev/shm", Data: "mode=1777,size=65536k", Flags: defaultMountFlags, }, { Source: "mqueue", Destination: "/dev/mqueue", Device: "mqueue", Flags: defaultMountFlags, }, { Source: "sysfs", Destination: "/sys", Device: "sysfs", Flags: defaultMountFlags | unix.MS_RDONLY, }, }, UidMappings: []configs.IDMap{ { ContainerID: 0, HostID: 1000, Size: 65536, }, }, GidMappings: []configs.IDMap{ { ContainerID: 0, HostID: 1000, Size: 65536, }, }, Networks: []*configs.Network{ { Type: "loopback", Address: "127.0.0.1/0", Gateway: "localhost", }, }, Rlimits: []configs.Rlimit{ { Type: unix.RLIMIT_NOFILE, Hard: uint64(1025), Soft: uint64(1025), }, }, } ``` Once you have the configuration populated you can create a container: ```go container, err := factory.Create("container-id", config) if err != nil { logrus.Fatal(err) return } ``` To spawn bash as the initial process inside the container and have the processes pid returned in order to wait, signal, or kill the process: ```go process := &libcontainer.Process{ Args: []string{"/bin/bash"}, Env: []string{"PATH=/bin"}, User: "daemon", Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, } err := container.Run(process) if err != nil { container.Destroy() logrus.Fatal(err) return } // wait for the process to finish. _, err := process.Wait() if err != nil { logrus.Fatal(err) } // destroy the container. container.Destroy() ``` Additional ways to interact with a running container are: ```go // return all the pids for all processes running inside the container. processes, err := container.Processes() // get detailed cpu, memory, io, and network statistics for the container and // it's processes. stats, err := container.Stats() // pause all processes inside the container. container.Pause() // resume all paused processes. container.Resume() // send signal to container's init process. container.Signal(signal) // update container resource constraints. container.Set(config) // get current status of the container. status, err := container.Status() // get current container's state information. state, err := container.State() ``` #### Checkpoint & Restore libcontainer now integrates [CRIU](http://criu.org/) for checkpointing and restoring containers. This let's you save the state of a process running inside a container to disk, and then restore that state into a new process, on the same machine or on another machine. `criu` version 1.5.2 or higher is required to use checkpoint and restore. If you don't already have `criu` installed, you can build it from source, following the [online instructions](http://criu.org/Installation). `criu` is also installed in the docker image generated when building libcontainer with docker. ## Copyright and license Code and documentation copyright 2014 Docker, inc. Code released under the Apache 2.0 license. Docs released under Creative commons. cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/SPEC.md000066400000000000000000000317471315410276000265050ustar00rootroot00000000000000## Container Specification - v1 This is the standard configuration for version 1 containers. It includes namespaces, standard filesystem setup, a default Linux capability set, and information about resource reservations. It also has information about any populated environment settings for the processes running inside a container. Along with the configuration of how a container is created the standard also discusses actions that can be performed on a container to manage and inspect information about the processes running inside. The v1 profile is meant to be able to accommodate the majority of applications with a strong security configuration. ### System Requirements and Compatibility Minimum requirements: * Kernel version - 3.10 recommended 2.6.2x minimum(with backported patches) * Mounted cgroups with each subsystem in its own hierarchy ### Namespaces | Flag | Enabled | | ------------ | ------- | | CLONE_NEWPID | 1 | | CLONE_NEWUTS | 1 | | CLONE_NEWIPC | 1 | | CLONE_NEWNET | 1 | | CLONE_NEWNS | 1 | | CLONE_NEWUSER | 1 | Namespaces are created for the container via the `clone` syscall. ### Filesystem A root filesystem must be provided to a container for execution. The container will use this root filesystem (rootfs) to jail and spawn processes inside where the binaries and system libraries are local to that directory. Any binaries to be executed must be contained within this rootfs. Mounts that happen inside the container are automatically cleaned up when the container exits as the mount namespace is destroyed and the kernel will unmount all the mounts that were setup within that namespace. For a container to execute properly there are certain filesystems that are required to be mounted within the rootfs that the runtime will setup. | Path | Type | Flags | Data | | ----------- | ------ | -------------------------------------- | ---------------------------------------- | | /proc | proc | MS_NOEXEC,MS_NOSUID,MS_NODEV | | | /dev | tmpfs | MS_NOEXEC,MS_STRICTATIME | mode=755 | | /dev/shm | tmpfs | MS_NOEXEC,MS_NOSUID,MS_NODEV | mode=1777,size=65536k | | /dev/mqueue | mqueue | MS_NOEXEC,MS_NOSUID,MS_NODEV | | | /dev/pts | devpts | MS_NOEXEC,MS_NOSUID | newinstance,ptmxmode=0666,mode=620,gid=5 | | /sys | sysfs | MS_NOEXEC,MS_NOSUID,MS_NODEV,MS_RDONLY | | After a container's filesystems are mounted within the newly created mount namespace `/dev` will need to be populated with a set of device nodes. It is expected that a rootfs does not need to have any device nodes specified for `/dev` within the rootfs as the container will setup the correct devices that are required for executing a container's process. | Path | Mode | Access | | ------------ | ---- | ---------- | | /dev/null | 0666 | rwm | | /dev/zero | 0666 | rwm | | /dev/full | 0666 | rwm | | /dev/tty | 0666 | rwm | | /dev/random | 0666 | rwm | | /dev/urandom | 0666 | rwm | **ptmx** `/dev/ptmx` will need to be a symlink to the host's `/dev/ptmx` within the container. The use of a pseudo TTY is optional within a container and it should support both. If a pseudo is provided to the container `/dev/console` will need to be setup by binding the console in `/dev/` after it has been populated and mounted in tmpfs. | Source | Destination | UID GID | Mode | Type | | --------------- | ------------ | ------- | ---- | ---- | | *pty host path* | /dev/console | 0 0 | 0600 | bind | After `/dev/null` has been setup we check for any external links between the container's io, STDIN, STDOUT, STDERR. If the container's io is pointing to `/dev/null` outside the container we close and `dup2` the `/dev/null` that is local to the container's rootfs. After the container has `/proc` mounted a few standard symlinks are setup within `/dev/` for the io. | Source | Destination | | --------------- | ----------- | | /proc/self/fd | /dev/fd | | /proc/self/fd/0 | /dev/stdin | | /proc/self/fd/1 | /dev/stdout | | /proc/self/fd/2 | /dev/stderr | A `pivot_root` is used to change the root for the process, effectively jailing the process inside the rootfs. ```c put_old = mkdir(...); pivot_root(rootfs, put_old); chdir("/"); unmount(put_old, MS_DETACH); rmdir(put_old); ``` For container's running with a rootfs inside `ramfs` a `MS_MOVE` combined with a `chroot` is required as `pivot_root` is not supported in `ramfs`. ```c mount(rootfs, "/", NULL, MS_MOVE, NULL); chroot("."); chdir("/"); ``` The `umask` is set back to `0022` after the filesystem setup has been completed. ### Resources Cgroups are used to handle resource allocation for containers. This includes system resources like cpu, memory, and device access. | Subsystem | Enabled | | ---------- | ------- | | devices | 1 | | memory | 1 | | cpu | 1 | | cpuacct | 1 | | cpuset | 1 | | blkio | 1 | | perf_event | 1 | | freezer | 1 | | hugetlb | 1 | | pids | 1 | All cgroup subsystem are joined so that statistics can be collected from each of the subsystems. Freezer does not expose any stats but is joined so that containers can be paused and resumed. The parent process of the container's init must place the init pid inside the correct cgroups before the initialization begins. This is done so that no processes or threads escape the cgroups. This sync is done via a pipe ( specified in the runtime section below ) that the container's init process will block waiting for the parent to finish setup. ### Security The standard set of Linux capabilities that are set in a container provide a good default for security and flexibility for the applications. | Capability | Enabled | | -------------------- | ------- | | CAP_NET_RAW | 1 | | CAP_NET_BIND_SERVICE | 1 | | CAP_AUDIT_READ | 1 | | CAP_AUDIT_WRITE | 1 | | CAP_DAC_OVERRIDE | 1 | | CAP_SETFCAP | 1 | | CAP_SETPCAP | 1 | | CAP_SETGID | 1 | | CAP_SETUID | 1 | | CAP_MKNOD | 1 | | CAP_CHOWN | 1 | | CAP_FOWNER | 1 | | CAP_FSETID | 1 | | CAP_KILL | 1 | | CAP_SYS_CHROOT | 1 | | CAP_NET_BROADCAST | 0 | | CAP_SYS_MODULE | 0 | | CAP_SYS_RAWIO | 0 | | CAP_SYS_PACCT | 0 | | CAP_SYS_ADMIN | 0 | | CAP_SYS_NICE | 0 | | CAP_SYS_RESOURCE | 0 | | CAP_SYS_TIME | 0 | | CAP_SYS_TTY_CONFIG | 0 | | CAP_AUDIT_CONTROL | 0 | | CAP_MAC_OVERRIDE | 0 | | CAP_MAC_ADMIN | 0 | | CAP_NET_ADMIN | 0 | | CAP_SYSLOG | 0 | | CAP_DAC_READ_SEARCH | 0 | | CAP_LINUX_IMMUTABLE | 0 | | CAP_IPC_LOCK | 0 | | CAP_IPC_OWNER | 0 | | CAP_SYS_PTRACE | 0 | | CAP_SYS_BOOT | 0 | | CAP_LEASE | 0 | | CAP_WAKE_ALARM | 0 | | CAP_BLOCK_SUSPEND | 0 | Additional security layers like [apparmor](https://wiki.ubuntu.com/AppArmor) and [selinux](http://selinuxproject.org/page/Main_Page) can be used with the containers. A container should support setting an apparmor profile or selinux process and mount labels if provided in the configuration. Standard apparmor profile: ```c #include profile flags=(attach_disconnected,mediate_deleted) { #include network, capability, file, umount, deny @{PROC}/sys/fs/** wklx, deny @{PROC}/sysrq-trigger rwklx, deny @{PROC}/mem rwklx, deny @{PROC}/kmem rwklx, deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx, deny @{PROC}/sys/kernel/*/** wklx, deny mount, deny /sys/[^f]*/** wklx, deny /sys/f[^s]*/** wklx, deny /sys/fs/[^c]*/** wklx, deny /sys/fs/c[^g]*/** wklx, deny /sys/fs/cg[^r]*/** wklx, deny /sys/firmware/efi/efivars/** rwklx, deny /sys/kernel/security/** rwklx, } ``` *TODO: seccomp work is being done to find a good default config* ### Runtime and Init Process During container creation the parent process needs to talk to the container's init process and have a form of synchronization. This is accomplished by creating a pipe that is passed to the container's init. When the init process first spawns it will block on its side of the pipe until the parent closes its side. This allows the parent to have time to set the new process inside a cgroup hierarchy and/or write any uid/gid mappings required for user namespaces. The pipe is passed to the init process via FD 3. The application consuming libcontainer should be compiled statically. libcontainer does not define any init process and the arguments provided are used to `exec` the process inside the application. There should be no long running init within the container spec. If a pseudo tty is provided to a container it will open and `dup2` the console as the container's STDIN, STDOUT, STDERR as well as mounting the console as `/dev/console`. An extra set of mounts are provided to a container and setup for use. A container's rootfs can contain some non portable files inside that can cause side effects during execution of a process. These files are usually created and populated with the container specific information via the runtime. **Extra runtime files:** * /etc/hosts * /etc/resolv.conf * /etc/hostname * /etc/localtime #### Defaults There are a few defaults that can be overridden by users, but in their omission these apply to processes within a container. | Type | Value | | ------------------- | ------------------------------ | | Parent Death Signal | SIGKILL | | UID | 0 | | GID | 0 | | GROUPS | 0, NULL | | CWD | "/" | | $HOME | Current user's home dir or "/" | | Readonly rootfs | false | | Pseudo TTY | false | ## Actions After a container is created there is a standard set of actions that can be done to the container. These actions are part of the public API for a container. | Action | Description | | -------------- | ------------------------------------------------------------------ | | Get processes | Return all the pids for processes running inside a container | | Get Stats | Return resource statistics for the container as a whole | | Wait | Waits on the container's init process ( pid 1 ) | | Wait Process | Wait on any of the container's processes returning the exit status | | Destroy | Kill the container's init process and remove any filesystem state | | Signal | Send a signal to the container's init process | | Signal Process | Send a signal to any of the container's processes | | Pause | Pause all processes inside the container | | Resume | Resume all processes inside the container if paused | | Exec | Execute a new process inside of the container ( requires setns ) | | Set | Setup configs of the container after it's created | ### Execute a new process inside of a running container. User can execute a new process inside of a running container. Any binaries to be executed must be accessible within the container's rootfs. The started process will run inside the container's rootfs. Any changes made by the process to the container's filesystem will persist after the process finished executing. The started process will join all the container's existing namespaces. When the container is paused, the process will also be paused and will resume when the container is unpaused. The started process will only run when the container's primary process (PID 1) is running, and will not be restarted when the container is restarted. #### Planned additions The started process will have its own cgroups nested inside the container's cgroups. This is used for process tracking and optionally resource allocation handling for the new process. Freezer cgroup is required, the rest of the cgroups are optional. The process executor must place its pid inside the correct cgroups before starting the process. This is done so that no child processes or threads can escape the cgroups. When the process is stopped, the process executor will try (in a best-effort way) to stop all its children and remove the sub-cgroups. cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/apparmor/000077500000000000000000000000001315410276000272365ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor.go000066400000000000000000000016751315410276000314170ustar00rootroot00000000000000// +build apparmor,linux package apparmor // #cgo LDFLAGS: -lapparmor // #include // #include import "C" import ( "fmt" "io/ioutil" "os" "unsafe" ) // IsEnabled returns true if apparmor is enabled for the host. func IsEnabled() bool { if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" { if _, err = os.Stat("/sbin/apparmor_parser"); err == nil { buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") return err == nil && len(buf) > 1 && buf[0] == 'Y' } } return false } // ApplyProfile will apply the profile with the specified name to the process after // the next exec. func ApplyProfile(name string) error { if name == "" { return nil } cName := C.CString(name) defer C.free(unsafe.Pointer(cName)) if _, err := C.aa_change_onexec(cName); err != nil { return fmt.Errorf("apparmor failed to apply profile: %s", err) } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor_disabled.go000066400000000000000000000004651315410276000332420ustar00rootroot00000000000000// +build !apparmor !linux package apparmor import ( "errors" ) var ErrApparmorNotEnabled = errors.New("apparmor: config provided but apparmor not supported") func IsEnabled() bool { return false } func ApplyProfile(name string) error { if name != "" { return ErrApparmorNotEnabled } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/capabilities_linux.go000066400000000000000000000057641315410276000316300ustar00rootroot00000000000000// +build linux package libcontainer import ( "fmt" "os" "strings" "github.com/opencontainers/runc/libcontainer/configs" "github.com/syndtr/gocapability/capability" ) const allCapabilityTypes = capability.CAPS | capability.BOUNDS | capability.AMBS var capabilityMap map[string]capability.Cap func init() { capabilityMap = make(map[string]capability.Cap) last := capability.CAP_LAST_CAP // workaround for RHEL6 which has no /proc/sys/kernel/cap_last_cap if last == capability.Cap(63) { last = capability.CAP_BLOCK_SUSPEND } for _, cap := range capability.List() { if cap > last { continue } capKey := fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String())) capabilityMap[capKey] = cap } } func newContainerCapList(capConfig *configs.Capabilities) (*containerCapabilities, error) { bounding := []capability.Cap{} for _, c := range capConfig.Bounding { v, ok := capabilityMap[c] if !ok { return nil, fmt.Errorf("unknown capability %q", c) } bounding = append(bounding, v) } effective := []capability.Cap{} for _, c := range capConfig.Effective { v, ok := capabilityMap[c] if !ok { return nil, fmt.Errorf("unknown capability %q", c) } effective = append(effective, v) } inheritable := []capability.Cap{} for _, c := range capConfig.Inheritable { v, ok := capabilityMap[c] if !ok { return nil, fmt.Errorf("unknown capability %q", c) } inheritable = append(inheritable, v) } permitted := []capability.Cap{} for _, c := range capConfig.Permitted { v, ok := capabilityMap[c] if !ok { return nil, fmt.Errorf("unknown capability %q", c) } permitted = append(permitted, v) } ambient := []capability.Cap{} for _, c := range capConfig.Ambient { v, ok := capabilityMap[c] if !ok { return nil, fmt.Errorf("unknown capability %q", c) } ambient = append(ambient, v) } pid, err := capability.NewPid(os.Getpid()) if err != nil { return nil, err } return &containerCapabilities{ bounding: bounding, effective: effective, inheritable: inheritable, permitted: permitted, ambient: ambient, pid: pid, }, nil } type containerCapabilities struct { pid capability.Capabilities bounding []capability.Cap effective []capability.Cap inheritable []capability.Cap permitted []capability.Cap ambient []capability.Cap } // ApplyBoundingSet sets the capability bounding set to those specified in the whitelist. func (c *containerCapabilities) ApplyBoundingSet() error { c.pid.Clear(capability.BOUNDS) c.pid.Set(capability.BOUNDS, c.bounding...) return c.pid.Apply(capability.BOUNDS) } // Apply sets all the capabilities for the current process in the config. func (c *containerCapabilities) ApplyCaps() error { c.pid.Clear(allCapabilityTypes) c.pid.Set(capability.BOUNDS, c.bounding...) c.pid.Set(capability.PERMITTED, c.permitted...) c.pid.Set(capability.INHERITABLE, c.inheritable...) c.pid.Set(capability.EFFECTIVE, c.effective...) c.pid.Set(capability.AMBIENT, c.ambient...) return c.pid.Apply(allCapabilityTypes) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/000077500000000000000000000000001315410276000270775ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go000066400000000000000000000026601315410276000311140ustar00rootroot00000000000000// +build linux package cgroups import ( "fmt" "github.com/opencontainers/runc/libcontainer/configs" ) type Manager interface { // Applies cgroup configuration to the process with the specified pid Apply(pid int) error // Returns the PIDs inside the cgroup set GetPids() ([]int, error) // Returns the PIDs inside the cgroup set & all sub-cgroups GetAllPids() ([]int, error) // Returns statistics for the cgroup set GetStats() (*Stats, error) // Toggles the freezer cgroup according with specified state Freeze(state configs.FreezerState) error // Destroys the cgroup set Destroy() error // The option func SystemdCgroups() and Cgroupfs() require following attributes: // Paths map[string]string // Cgroups *configs.Cgroup // Paths maps cgroup subsystem to path at which it is mounted. // Cgroups specifies specific cgroup settings for the various subsystems // Returns cgroup paths to save in a state file and to be able to // restore the object later. GetPaths() map[string]string // Sets the cgroup as configured. Set(container *configs.Config) error } type NotFoundError struct { Subsystem string } func (e *NotFoundError) Error() string { return fmt.Sprintf("mountpoint for %s not found", e.Subsystem) } func NewNotFoundError(sub string) error { return &NotFoundError{ Subsystem: sub, } } func IsNotFound(err error) bool { if err == nil { return false } _, ok := err.(*NotFoundError) return ok } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups_unsupported.go000066400000000000000000000000421315410276000335540ustar00rootroot00000000000000// +build !linux package cgroups cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/000077500000000000000000000000001315410276000275075ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go000066400000000000000000000200651315410276000320370ustar00rootroot00000000000000// +build linux package fs import ( "errors" "fmt" "io" "io/ioutil" "os" "path/filepath" "sync" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" ) var ( subsystems = subsystemSet{ &CpusetGroup{}, &DevicesGroup{}, &MemoryGroup{}, &CpuGroup{}, &CpuacctGroup{}, &PidsGroup{}, &BlkioGroup{}, &HugetlbGroup{}, &NetClsGroup{}, &NetPrioGroup{}, &PerfEventGroup{}, &FreezerGroup{}, &NameGroup{GroupName: "name=systemd", Join: true}, } HugePageSizes, _ = cgroups.GetHugePageSize() ) var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist") type subsystemSet []subsystem func (s subsystemSet) Get(name string) (subsystem, error) { for _, ss := range s { if ss.Name() == name { return ss, nil } } return nil, errSubsystemDoesNotExist } type subsystem interface { // Name returns the name of the subsystem. Name() string // Returns the stats, as 'stats', corresponding to the cgroup under 'path'. GetStats(path string, stats *cgroups.Stats) error // Removes the cgroup represented by 'cgroupData'. Remove(*cgroupData) error // Creates and joins the cgroup represented by 'cgroupData'. Apply(*cgroupData) error // Set the cgroup represented by cgroup. Set(path string, cgroup *configs.Cgroup) error } type Manager struct { mu sync.Mutex Cgroups *configs.Cgroup Paths map[string]string } // The absolute path to the root of the cgroup hierarchies. var cgroupRootLock sync.Mutex var cgroupRoot string // Gets the cgroupRoot. func getCgroupRoot() (string, error) { cgroupRootLock.Lock() defer cgroupRootLock.Unlock() if cgroupRoot != "" { return cgroupRoot, nil } root, err := cgroups.FindCgroupMountpointDir() if err != nil { return "", err } if _, err := os.Stat(root); err != nil { return "", err } cgroupRoot = root return cgroupRoot, nil } type cgroupData struct { root string innerPath string config *configs.Cgroup pid int } func (m *Manager) Apply(pid int) (err error) { if m.Cgroups == nil { return nil } m.mu.Lock() defer m.mu.Unlock() var c = m.Cgroups d, err := getCgroupData(m.Cgroups, pid) if err != nil { return err } m.Paths = make(map[string]string) if c.Paths != nil { for name, path := range c.Paths { _, err := d.path(name) if err != nil { if cgroups.IsNotFound(err) { continue } return err } m.Paths[name] = path } return cgroups.EnterPid(m.Paths, pid) } for _, sys := range subsystems { // TODO: Apply should, ideally, be reentrant or be broken up into a separate // create and join phase so that the cgroup hierarchy for a container can be // created then join consists of writing the process pids to cgroup.procs p, err := d.path(sys.Name()) if err != nil { // The non-presence of the devices subsystem is // considered fatal for security reasons. if cgroups.IsNotFound(err) && sys.Name() != "devices" { continue } return err } m.Paths[sys.Name()] = p if err := sys.Apply(d); err != nil { return err } } return nil } func (m *Manager) Destroy() error { if m.Cgroups.Paths != nil { return nil } m.mu.Lock() defer m.mu.Unlock() if err := cgroups.RemovePaths(m.Paths); err != nil { return err } m.Paths = make(map[string]string) return nil } func (m *Manager) GetPaths() map[string]string { m.mu.Lock() paths := m.Paths m.mu.Unlock() return paths } func (m *Manager) GetStats() (*cgroups.Stats, error) { m.mu.Lock() defer m.mu.Unlock() stats := cgroups.NewStats() for name, path := range m.Paths { sys, err := subsystems.Get(name) if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) { continue } if err := sys.GetStats(path, stats); err != nil { return nil, err } } return stats, nil } func (m *Manager) Set(container *configs.Config) error { // If Paths are set, then we are just joining cgroups paths // and there is no need to set any values. if m.Cgroups.Paths != nil { return nil } paths := m.GetPaths() for _, sys := range subsystems { path := paths[sys.Name()] if err := sys.Set(path, container.Cgroups); err != nil { return err } } if m.Paths["cpu"] != "" { if err := CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); err != nil { return err } } return nil } // Freeze toggles the container's freezer cgroup depending on the state // provided func (m *Manager) Freeze(state configs.FreezerState) error { paths := m.GetPaths() dir := paths["freezer"] prevState := m.Cgroups.Resources.Freezer m.Cgroups.Resources.Freezer = state freezer, err := subsystems.Get("freezer") if err != nil { return err } err = freezer.Set(dir, m.Cgroups) if err != nil { m.Cgroups.Resources.Freezer = prevState return err } return nil } func (m *Manager) GetPids() ([]int, error) { paths := m.GetPaths() return cgroups.GetPids(paths["devices"]) } func (m *Manager) GetAllPids() ([]int, error) { paths := m.GetPaths() return cgroups.GetAllPids(paths["devices"]) } func getCgroupData(c *configs.Cgroup, pid int) (*cgroupData, error) { root, err := getCgroupRoot() if err != nil { return nil, err } if (c.Name != "" || c.Parent != "") && c.Path != "" { return nil, fmt.Errorf("cgroup: either Path or Name and Parent should be used") } // XXX: Do not remove this code. Path safety is important! -- cyphar cgPath := libcontainerUtils.CleanPath(c.Path) cgParent := libcontainerUtils.CleanPath(c.Parent) cgName := libcontainerUtils.CleanPath(c.Name) innerPath := cgPath if innerPath == "" { innerPath = filepath.Join(cgParent, cgName) } return &cgroupData{ root: root, innerPath: innerPath, config: c, pid: pid, }, nil } func (raw *cgroupData) path(subsystem string) (string, error) { mnt, err := cgroups.FindCgroupMountpoint(subsystem) // If we didn't mount the subsystem, there is no point we make the path. if err != nil { return "", err } // If the cgroup name/path is absolute do not look relative to the cgroup of the init process. if filepath.IsAbs(raw.innerPath) { // Sometimes subsystems can be mounted together as 'cpu,cpuacct'. return filepath.Join(raw.root, filepath.Base(mnt), raw.innerPath), nil } // Use GetOwnCgroupPath instead of GetInitCgroupPath, because the creating // process could in container and shared pid namespace with host, and // /proc/1/cgroup could point to whole other world of cgroups. parentPath, err := cgroups.GetOwnCgroupPath(subsystem) if err != nil { return "", err } return filepath.Join(parentPath, raw.innerPath), nil } func (raw *cgroupData) join(subsystem string) (string, error) { path, err := raw.path(subsystem) if err != nil { return "", err } if err := os.MkdirAll(path, 0755); err != nil { return "", err } if err := cgroups.WriteCgroupProc(path, raw.pid); err != nil { return "", err } return path, nil } func writeFile(dir, file, data string) error { // Normally dir should not be empty, one case is that cgroup subsystem // is not mounted, we will get empty dir, and we want it fail here. if dir == "" { return fmt.Errorf("no such directory for %s", file) } if err := ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700); err != nil { return fmt.Errorf("failed to write %v to %v: %v", data, file, err) } return nil } func readFile(dir, file string) (string, error) { data, err := ioutil.ReadFile(filepath.Join(dir, file)) return string(data), err } func removePath(p string, err error) error { if err != nil { return err } if p != "" { return os.RemoveAll(p) } return nil } func CheckCpushares(path string, c uint64) error { var cpuShares uint64 if c == 0 { return nil } fd, err := os.Open(filepath.Join(path, "cpu.shares")) if err != nil { return err } defer fd.Close() _, err = fmt.Fscanf(fd, "%d", &cpuShares) if err != nil && err != io.EOF { return err } if c > cpuShares { return fmt.Errorf("The maximum allowed cpu-shares is %d", cpuShares) } else if c < cpuShares { return fmt.Errorf("The minimum allowed cpu-shares is %d", cpuShares) } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go000066400000000000000000000134671315410276000311510ustar00rootroot00000000000000// +build linux package fs import ( "bufio" "fmt" "os" "path/filepath" "strconv" "strings" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type BlkioGroup struct { } func (s *BlkioGroup) Name() string { return "blkio" } func (s *BlkioGroup) Apply(d *cgroupData) error { _, err := d.join("blkio") if err != nil && !cgroups.IsNotFound(err) { return err } return nil } func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error { if cgroup.Resources.BlkioWeight != 0 { if err := writeFile(path, "blkio.weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioWeight), 10)); err != nil { return err } } if cgroup.Resources.BlkioLeafWeight != 0 { if err := writeFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioLeafWeight), 10)); err != nil { return err } } for _, wd := range cgroup.Resources.BlkioWeightDevice { if err := writeFile(path, "blkio.weight_device", wd.WeightString()); err != nil { return err } if err := writeFile(path, "blkio.leaf_weight_device", wd.LeafWeightString()); err != nil { return err } } for _, td := range cgroup.Resources.BlkioThrottleReadBpsDevice { if err := writeFile(path, "blkio.throttle.read_bps_device", td.String()); err != nil { return err } } for _, td := range cgroup.Resources.BlkioThrottleWriteBpsDevice { if err := writeFile(path, "blkio.throttle.write_bps_device", td.String()); err != nil { return err } } for _, td := range cgroup.Resources.BlkioThrottleReadIOPSDevice { if err := writeFile(path, "blkio.throttle.read_iops_device", td.String()); err != nil { return err } } for _, td := range cgroup.Resources.BlkioThrottleWriteIOPSDevice { if err := writeFile(path, "blkio.throttle.write_iops_device", td.String()); err != nil { return err } } return nil } func (s *BlkioGroup) Remove(d *cgroupData) error { return removePath(d.path("blkio")) } /* examples: blkio.sectors 8:0 6792 blkio.io_service_bytes 8:0 Read 1282048 8:0 Write 2195456 8:0 Sync 2195456 8:0 Async 1282048 8:0 Total 3477504 Total 3477504 blkio.io_serviced 8:0 Read 124 8:0 Write 104 8:0 Sync 104 8:0 Async 124 8:0 Total 228 Total 228 blkio.io_queued 8:0 Read 0 8:0 Write 0 8:0 Sync 0 8:0 Async 0 8:0 Total 0 Total 0 */ func splitBlkioStatLine(r rune) bool { return r == ' ' || r == ':' } func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) { var blkioStats []cgroups.BlkioStatEntry f, err := os.Open(path) if err != nil { if os.IsNotExist(err) { return blkioStats, nil } return nil, err } defer f.Close() sc := bufio.NewScanner(f) for sc.Scan() { // format: dev type amount fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine) if len(fields) < 3 { if len(fields) == 2 && fields[0] == "Total" { // skip total line continue } else { return nil, fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text()) } } v, err := strconv.ParseUint(fields[0], 10, 64) if err != nil { return nil, err } major := v v, err = strconv.ParseUint(fields[1], 10, 64) if err != nil { return nil, err } minor := v op := "" valueField := 2 if len(fields) == 4 { op = fields[2] valueField = 3 } v, err = strconv.ParseUint(fields[valueField], 10, 64) if err != nil { return nil, err } blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v}) } return blkioStats, nil } func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error { // Try to read CFQ stats available on all CFQ enabled kernels first if blkioStats, err := getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err == nil && blkioStats != nil { return getCFQStats(path, stats) } return getStats(path, stats) // Use generic stats as fallback } func getCFQStats(path string, stats *cgroups.Stats) error { var blkioStats []cgroups.BlkioStatEntry var err error if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil { return err } stats.BlkioStats.SectorsRecursive = blkioStats if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_bytes_recursive")); err != nil { return err } stats.BlkioStats.IoServiceBytesRecursive = blkioStats if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err != nil { return err } stats.BlkioStats.IoServicedRecursive = blkioStats if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_queued_recursive")); err != nil { return err } stats.BlkioStats.IoQueuedRecursive = blkioStats if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_time_recursive")); err != nil { return err } stats.BlkioStats.IoServiceTimeRecursive = blkioStats if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_wait_time_recursive")); err != nil { return err } stats.BlkioStats.IoWaitTimeRecursive = blkioStats if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_merged_recursive")); err != nil { return err } stats.BlkioStats.IoMergedRecursive = blkioStats if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.time_recursive")); err != nil { return err } stats.BlkioStats.IoTimeRecursive = blkioStats return nil } func getStats(path string, stats *cgroups.Stats) error { var blkioStats []cgroups.BlkioStatEntry var err error if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_service_bytes")); err != nil { return err } stats.BlkioStats.IoServiceBytesRecursive = blkioStats if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_serviced")); err != nil { return err } stats.BlkioStats.IoServicedRecursive = blkioStats return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go000066400000000000000000000060201315410276000306230ustar00rootroot00000000000000// +build linux package fs import ( "bufio" "os" "path/filepath" "strconv" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type CpuGroup struct { } func (s *CpuGroup) Name() string { return "cpu" } func (s *CpuGroup) Apply(d *cgroupData) error { // We always want to join the cpu group, to allow fair cpu scheduling // on a container basis path, err := d.path("cpu") if err != nil && !cgroups.IsNotFound(err) { return err } return s.ApplyDir(path, d.config, d.pid) } func (s *CpuGroup) ApplyDir(path string, cgroup *configs.Cgroup, pid int) error { // This might happen if we have no cpu cgroup mounted. // Just do nothing and don't fail. if path == "" { return nil } if err := os.MkdirAll(path, 0755); err != nil { return err } // We should set the real-Time group scheduling settings before moving // in the process because if the process is already in SCHED_RR mode // and no RT bandwidth is set, adding it will fail. if err := s.SetRtSched(path, cgroup); err != nil { return err } // because we are not using d.join we need to place the pid into the procs file // unlike the other subsystems if err := cgroups.WriteCgroupProc(path, pid); err != nil { return err } return nil } func (s *CpuGroup) SetRtSched(path string, cgroup *configs.Cgroup) error { if cgroup.Resources.CpuRtPeriod != 0 { if err := writeFile(path, "cpu.rt_period_us", strconv.FormatUint(cgroup.Resources.CpuRtPeriod, 10)); err != nil { return err } } if cgroup.Resources.CpuRtRuntime != 0 { if err := writeFile(path, "cpu.rt_runtime_us", strconv.FormatInt(cgroup.Resources.CpuRtRuntime, 10)); err != nil { return err } } return nil } func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error { if cgroup.Resources.CpuShares != 0 { if err := writeFile(path, "cpu.shares", strconv.FormatUint(cgroup.Resources.CpuShares, 10)); err != nil { return err } } if cgroup.Resources.CpuPeriod != 0 { if err := writeFile(path, "cpu.cfs_period_us", strconv.FormatUint(cgroup.Resources.CpuPeriod, 10)); err != nil { return err } } if cgroup.Resources.CpuQuota != 0 { if err := writeFile(path, "cpu.cfs_quota_us", strconv.FormatInt(cgroup.Resources.CpuQuota, 10)); err != nil { return err } } if err := s.SetRtSched(path, cgroup); err != nil { return err } return nil } func (s *CpuGroup) Remove(d *cgroupData) error { return removePath(d.path("cpu")) } func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error { f, err := os.Open(filepath.Join(path, "cpu.stat")) if err != nil { if os.IsNotExist(err) { return nil } return err } defer f.Close() sc := bufio.NewScanner(f) for sc.Scan() { t, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { return err } switch t { case "nr_periods": stats.CpuStats.ThrottlingData.Periods = v case "nr_throttled": stats.CpuStats.ThrottlingData.ThrottledPeriods = v case "throttled_time": stats.CpuStats.ThrottlingData.ThrottledTime = v } } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuacct.go000066400000000000000000000061441315410276000314650ustar00rootroot00000000000000// +build linux package fs import ( "fmt" "io/ioutil" "path/filepath" "strconv" "strings" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/system" ) const ( cgroupCpuacctStat = "cpuacct.stat" nanosecondsInSecond = 1000000000 ) var clockTicks = uint64(system.GetClockTicks()) type CpuacctGroup struct { } func (s *CpuacctGroup) Name() string { return "cpuacct" } func (s *CpuacctGroup) Apply(d *cgroupData) error { // we just want to join this group even though we don't set anything if _, err := d.join("cpuacct"); err != nil && !cgroups.IsNotFound(err) { return err } return nil } func (s *CpuacctGroup) Set(path string, cgroup *configs.Cgroup) error { return nil } func (s *CpuacctGroup) Remove(d *cgroupData) error { return removePath(d.path("cpuacct")) } func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error { userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path) if err != nil { return err } totalUsage, err := getCgroupParamUint(path, "cpuacct.usage") if err != nil { return err } percpuUsage, err := getPercpuUsage(path) if err != nil { return err } stats.CpuStats.CpuUsage.TotalUsage = totalUsage stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage return nil } // Returns user and kernel usage breakdown in nanoseconds. func getCpuUsageBreakdown(path string) (uint64, uint64, error) { userModeUsage := uint64(0) kernelModeUsage := uint64(0) const ( userField = "user" systemField = "system" ) // Expected format: // user // system data, err := ioutil.ReadFile(filepath.Join(path, cgroupCpuacctStat)) if err != nil { return 0, 0, err } fields := strings.Fields(string(data)) if len(fields) != 4 { return 0, 0, fmt.Errorf("failure - %s is expected to have 4 fields", filepath.Join(path, cgroupCpuacctStat)) } if fields[0] != userField { return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[0], cgroupCpuacctStat, userField) } if fields[2] != systemField { return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[2], cgroupCpuacctStat, systemField) } if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil { return 0, 0, err } if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil { return 0, 0, err } return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil } func getPercpuUsage(path string) ([]uint64, error) { percpuUsage := []uint64{} data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu")) if err != nil { return percpuUsage, err } for _, value := range strings.Fields(string(data)) { value, err := strconv.ParseUint(value, 10, 64) if err != nil { return percpuUsage, fmt.Errorf("Unable to convert param value to uint64: %s", err) } percpuUsage = append(percpuUsage, value) } return percpuUsage, nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go000066400000000000000000000105431315410276000313440ustar00rootroot00000000000000// +build linux package fs import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" ) type CpusetGroup struct { } func (s *CpusetGroup) Name() string { return "cpuset" } func (s *CpusetGroup) Apply(d *cgroupData) error { dir, err := d.path("cpuset") if err != nil && !cgroups.IsNotFound(err) { return err } return s.ApplyDir(dir, d.config, d.pid) } func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error { if cgroup.Resources.CpusetCpus != "" { if err := writeFile(path, "cpuset.cpus", cgroup.Resources.CpusetCpus); err != nil { return err } } if cgroup.Resources.CpusetMems != "" { if err := writeFile(path, "cpuset.mems", cgroup.Resources.CpusetMems); err != nil { return err } } return nil } func (s *CpusetGroup) Remove(d *cgroupData) error { return removePath(d.path("cpuset")) } func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error { return nil } func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) error { // This might happen if we have no cpuset cgroup mounted. // Just do nothing and don't fail. if dir == "" { return nil } mountInfo, err := ioutil.ReadFile("/proc/self/mountinfo") if err != nil { return err } root := filepath.Dir(cgroups.GetClosestMountpointAncestor(dir, string(mountInfo))) // 'ensureParent' start with parent because we don't want to // explicitly inherit from parent, it could conflict with // 'cpuset.cpu_exclusive'. if err := s.ensureParent(filepath.Dir(dir), root); err != nil { return err } if err := os.MkdirAll(dir, 0755); err != nil { return err } // We didn't inherit cpuset configs from parent, but we have // to ensure cpuset configs are set before moving task into the // cgroup. // The logic is, if user specified cpuset configs, use these // specified configs, otherwise, inherit from parent. This makes // cpuset configs work correctly with 'cpuset.cpu_exclusive', and // keep backward compatbility. if err := s.ensureCpusAndMems(dir, cgroup); err != nil { return err } // because we are not using d.join we need to place the pid into the procs file // unlike the other subsystems if err := cgroups.WriteCgroupProc(dir, pid); err != nil { return err } return nil } func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) { if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil { return } if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil { return } return cpus, mems, nil } // ensureParent makes sure that the parent directory of current is created // and populated with the proper cpus and mems files copied from // it's parent. func (s *CpusetGroup) ensureParent(current, root string) error { parent := filepath.Dir(current) if libcontainerUtils.CleanPath(parent) == root { return nil } // Avoid infinite recursion. if parent == current { return fmt.Errorf("cpuset: cgroup parent path outside cgroup root") } if err := s.ensureParent(parent, root); err != nil { return err } if err := os.MkdirAll(current, 0755); err != nil { return err } return s.copyIfNeeded(current, parent) } // copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent // directory to the current directory if the file's contents are 0 func (s *CpusetGroup) copyIfNeeded(current, parent string) error { var ( err error currentCpus, currentMems []byte parentCpus, parentMems []byte ) if currentCpus, currentMems, err = s.getSubsystemSettings(current); err != nil { return err } if parentCpus, parentMems, err = s.getSubsystemSettings(parent); err != nil { return err } if s.isEmpty(currentCpus) { if err := writeFile(current, "cpuset.cpus", string(parentCpus)); err != nil { return err } } if s.isEmpty(currentMems) { if err := writeFile(current, "cpuset.mems", string(parentMems)); err != nil { return err } } return nil } func (s *CpusetGroup) isEmpty(b []byte) bool { return len(bytes.Trim(b, "\n")) == 0 } func (s *CpusetGroup) ensureCpusAndMems(path string, cgroup *configs.Cgroup) error { if err := s.Set(path, cgroup); err != nil { return err } return s.copyIfNeeded(path, filepath.Dir(path)) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go000066400000000000000000000033211315410276000314570ustar00rootroot00000000000000// +build linux package fs import ( "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/system" ) type DevicesGroup struct { } func (s *DevicesGroup) Name() string { return "devices" } func (s *DevicesGroup) Apply(d *cgroupData) error { _, err := d.join("devices") if err != nil { // We will return error even it's `not found` error, devices // cgroup is hard requirement for container's security. return err } return nil } func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { if system.RunningInUserNS() { return nil } devices := cgroup.Resources.Devices if len(devices) > 0 { for _, dev := range devices { file := "devices.deny" if dev.Allow { file = "devices.allow" } if err := writeFile(path, file, dev.CgroupString()); err != nil { return err } } return nil } if cgroup.Resources.AllowAllDevices != nil { if *cgroup.Resources.AllowAllDevices == false { if err := writeFile(path, "devices.deny", "a"); err != nil { return err } for _, dev := range cgroup.Resources.AllowedDevices { if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil { return err } } return nil } if err := writeFile(path, "devices.allow", "a"); err != nil { return err } } for _, dev := range cgroup.Resources.DeniedDevices { if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil { return err } } return nil } func (s *DevicesGroup) Remove(d *cgroupData) error { return removePath(d.path("devices")) } func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error { return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go000066400000000000000000000023401315410276000314770ustar00rootroot00000000000000// +build linux package fs import ( "fmt" "strings" "time" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type FreezerGroup struct { } func (s *FreezerGroup) Name() string { return "freezer" } func (s *FreezerGroup) Apply(d *cgroupData) error { _, err := d.join("freezer") if err != nil && !cgroups.IsNotFound(err) { return err } return nil } func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error { switch cgroup.Resources.Freezer { case configs.Frozen, configs.Thawed: if err := writeFile(path, "freezer.state", string(cgroup.Resources.Freezer)); err != nil { return err } for { state, err := readFile(path, "freezer.state") if err != nil { return err } if strings.TrimSpace(state) == string(cgroup.Resources.Freezer) { break } time.Sleep(1 * time.Millisecond) } case configs.Undefined: return nil default: return fmt.Errorf("Invalid argument '%s' to freezer.state", string(cgroup.Resources.Freezer)) } return nil } func (s *FreezerGroup) Remove(d *cgroupData) error { return removePath(d.path("freezer")) } func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error { return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/fs_unsupported.go000066400000000000000000000000351315410276000331140ustar00rootroot00000000000000// +build !linux package fs cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go000066400000000000000000000034031315410276000314700ustar00rootroot00000000000000// +build linux package fs import ( "fmt" "strconv" "strings" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type HugetlbGroup struct { } func (s *HugetlbGroup) Name() string { return "hugetlb" } func (s *HugetlbGroup) Apply(d *cgroupData) error { _, err := d.join("hugetlb") if err != nil && !cgroups.IsNotFound(err) { return err } return nil } func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error { for _, hugetlb := range cgroup.Resources.HugetlbLimit { if err := writeFile(path, strings.Join([]string{"hugetlb", hugetlb.Pagesize, "limit_in_bytes"}, "."), strconv.FormatUint(hugetlb.Limit, 10)); err != nil { return err } } return nil } func (s *HugetlbGroup) Remove(d *cgroupData) error { return removePath(d.path("hugetlb")) } func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error { hugetlbStats := cgroups.HugetlbStats{} for _, pageSize := range HugePageSizes { usage := strings.Join([]string{"hugetlb", pageSize, "usage_in_bytes"}, ".") value, err := getCgroupParamUint(path, usage) if err != nil { return fmt.Errorf("failed to parse %s - %v", usage, err) } hugetlbStats.Usage = value maxUsage := strings.Join([]string{"hugetlb", pageSize, "max_usage_in_bytes"}, ".") value, err = getCgroupParamUint(path, maxUsage) if err != nil { return fmt.Errorf("failed to parse %s - %v", maxUsage, err) } hugetlbStats.MaxUsage = value failcnt := strings.Join([]string{"hugetlb", pageSize, "failcnt"}, ".") value, err = getCgroupParamUint(path, failcnt) if err != nil { return fmt.Errorf("failed to parse %s - %v", failcnt, err) } hugetlbStats.Failcnt = value stats.HugetlbStats[pageSize] = hugetlbStats } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go000066400000000000000000000224741315410276000313570ustar00rootroot00000000000000// +build linux package fs import ( "bufio" "fmt" "io/ioutil" "os" "path/filepath" "strconv" "strings" "syscall" // only for Errno "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "golang.org/x/sys/unix" ) const ( cgroupKernelMemoryLimit = "memory.kmem.limit_in_bytes" cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes" cgroupMemoryLimit = "memory.limit_in_bytes" ) type MemoryGroup struct { } func (s *MemoryGroup) Name() string { return "memory" } func (s *MemoryGroup) Apply(d *cgroupData) (err error) { path, err := d.path("memory") if err != nil && !cgroups.IsNotFound(err) { return err } else if path == "" { return nil } if memoryAssigned(d.config) { if _, err := os.Stat(path); os.IsNotExist(err) { if err := os.MkdirAll(path, 0755); err != nil { return err } // Only enable kernel memory accouting when this cgroup // is created by libcontainer, otherwise we might get // error when people use `cgroupsPath` to join an existed // cgroup whose kernel memory is not initialized. if err := EnableKernelMemoryAccounting(path); err != nil { return err } } } defer func() { if err != nil { os.RemoveAll(path) } }() // We need to join memory cgroup after set memory limits, because // kmem.limit_in_bytes can only be set when the cgroup is empty. _, err = d.join("memory") if err != nil && !cgroups.IsNotFound(err) { return err } return nil } func EnableKernelMemoryAccounting(path string) error { // Check if kernel memory is enabled // We have to limit the kernel memory here as it won't be accounted at all // until a limit is set on the cgroup and limit cannot be set once the // cgroup has children, or if there are already tasks in the cgroup. for _, i := range []int64{1, -1} { if err := setKernelMemory(path, i); err != nil { return err } } return nil } func setKernelMemory(path string, kernelMemoryLimit int64) error { if path == "" { return fmt.Errorf("no such directory for %s", cgroupKernelMemoryLimit) } if !cgroups.PathExists(filepath.Join(path, cgroupKernelMemoryLimit)) { // kernel memory is not enabled on the system so we should do nothing return nil } if err := ioutil.WriteFile(filepath.Join(path, cgroupKernelMemoryLimit), []byte(strconv.FormatInt(kernelMemoryLimit, 10)), 0700); err != nil { // Check if the error number returned by the syscall is "EBUSY" // The EBUSY signal is returned on attempts to write to the // memory.kmem.limit_in_bytes file if the cgroup has children or // once tasks have been attached to the cgroup if pathErr, ok := err.(*os.PathError); ok { if errNo, ok := pathErr.Err.(syscall.Errno); ok { if errNo == unix.EBUSY { return fmt.Errorf("failed to set %s, because either tasks have already joined this cgroup or it has children", cgroupKernelMemoryLimit) } } } return fmt.Errorf("failed to write %v to %v: %v", kernelMemoryLimit, cgroupKernelMemoryLimit, err) } return nil } func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error { // If the memory update is set to -1 we should also // set swap to -1, it means unlimited memory. if cgroup.Resources.Memory == -1 { // Only set swap if it's enabled in kernel if cgroups.PathExists(filepath.Join(path, cgroupMemorySwapLimit)) { cgroup.Resources.MemorySwap = -1 } } // When memory and swap memory are both set, we need to handle the cases // for updating container. if cgroup.Resources.Memory != 0 && cgroup.Resources.MemorySwap != 0 { memoryUsage, err := getMemoryData(path, "") if err != nil { return err } // When update memory limit, we should adapt the write sequence // for memory and swap memory, so it won't fail because the new // value and the old value don't fit kernel's validation. if cgroup.Resources.MemorySwap == -1 || memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) { if err := writeFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { return err } if err := writeFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { return err } } else { if err := writeFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { return err } if err := writeFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { return err } } } else { if cgroup.Resources.Memory != 0 { if err := writeFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { return err } } if cgroup.Resources.MemorySwap != 0 { if err := writeFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { return err } } } return nil } func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error { if err := setMemoryAndSwap(path, cgroup); err != nil { return err } if cgroup.Resources.KernelMemory != 0 { if err := setKernelMemory(path, cgroup.Resources.KernelMemory); err != nil { return err } } if cgroup.Resources.MemoryReservation != 0 { if err := writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemoryReservation, 10)); err != nil { return err } } if cgroup.Resources.KernelMemoryTCP != 0 { if err := writeFile(path, "memory.kmem.tcp.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemoryTCP, 10)); err != nil { return err } } if cgroup.Resources.OomKillDisable { if err := writeFile(path, "memory.oom_control", "1"); err != nil { return err } } if cgroup.Resources.MemorySwappiness == nil || int64(*cgroup.Resources.MemorySwappiness) == -1 { return nil } else if *cgroup.Resources.MemorySwappiness <= 100 { if err := writeFile(path, "memory.swappiness", strconv.FormatUint(*cgroup.Resources.MemorySwappiness, 10)); err != nil { return err } } else { return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", *cgroup.Resources.MemorySwappiness) } return nil } func (s *MemoryGroup) Remove(d *cgroupData) error { return removePath(d.path("memory")) } func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error { // Set stats from memory.stat. statsFile, err := os.Open(filepath.Join(path, "memory.stat")) if err != nil { if os.IsNotExist(err) { return nil } return err } defer statsFile.Close() sc := bufio.NewScanner(statsFile) for sc.Scan() { t, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { return fmt.Errorf("failed to parse memory.stat (%q) - %v", sc.Text(), err) } stats.MemoryStats.Stats[t] = v } stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"] memoryUsage, err := getMemoryData(path, "") if err != nil { return err } stats.MemoryStats.Usage = memoryUsage swapUsage, err := getMemoryData(path, "memsw") if err != nil { return err } stats.MemoryStats.SwapUsage = swapUsage kernelUsage, err := getMemoryData(path, "kmem") if err != nil { return err } stats.MemoryStats.KernelUsage = kernelUsage kernelTCPUsage, err := getMemoryData(path, "kmem.tcp") if err != nil { return err } stats.MemoryStats.KernelTCPUsage = kernelTCPUsage useHierarchy := strings.Join([]string{"memory", "use_hierarchy"}, ".") value, err := getCgroupParamUint(path, useHierarchy) if err != nil { return err } if value == 1 { stats.MemoryStats.UseHierarchy = true } return nil } func memoryAssigned(cgroup *configs.Cgroup) bool { return cgroup.Resources.Memory != 0 || cgroup.Resources.MemoryReservation != 0 || cgroup.Resources.MemorySwap > 0 || cgroup.Resources.KernelMemory > 0 || cgroup.Resources.KernelMemoryTCP > 0 || cgroup.Resources.OomKillDisable || (cgroup.Resources.MemorySwappiness != nil && int64(*cgroup.Resources.MemorySwappiness) != -1) } func getMemoryData(path, name string) (cgroups.MemoryData, error) { memoryData := cgroups.MemoryData{} moduleName := "memory" if name != "" { moduleName = strings.Join([]string{"memory", name}, ".") } usage := strings.Join([]string{moduleName, "usage_in_bytes"}, ".") maxUsage := strings.Join([]string{moduleName, "max_usage_in_bytes"}, ".") failcnt := strings.Join([]string{moduleName, "failcnt"}, ".") limit := strings.Join([]string{moduleName, "limit_in_bytes"}, ".") value, err := getCgroupParamUint(path, usage) if err != nil { if moduleName != "memory" && os.IsNotExist(err) { return cgroups.MemoryData{}, nil } return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", usage, err) } memoryData.Usage = value value, err = getCgroupParamUint(path, maxUsage) if err != nil { if moduleName != "memory" && os.IsNotExist(err) { return cgroups.MemoryData{}, nil } return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", maxUsage, err) } memoryData.MaxUsage = value value, err = getCgroupParamUint(path, failcnt) if err != nil { if moduleName != "memory" && os.IsNotExist(err) { return cgroups.MemoryData{}, nil } return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", failcnt, err) } memoryData.Failcnt = value value, err = getCgroupParamUint(path, limit) if err != nil { if moduleName != "memory" && os.IsNotExist(err) { return cgroups.MemoryData{}, nil } return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", limit, err) } memoryData.Limit = value return memoryData, nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/name.go000066400000000000000000000013101315410276000307510ustar00rootroot00000000000000// +build linux package fs import ( "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type NameGroup struct { GroupName string Join bool } func (s *NameGroup) Name() string { return s.GroupName } func (s *NameGroup) Apply(d *cgroupData) error { if s.Join { // ignore errors if the named cgroup does not exist d.join(s.GroupName) } return nil } func (s *NameGroup) Set(path string, cgroup *configs.Cgroup) error { return nil } func (s *NameGroup) Remove(d *cgroupData) error { if s.Join { removePath(d.path(s.GroupName)) } return nil } func (s *NameGroup) GetStats(path string, stats *cgroups.Stats) error { return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go000066400000000000000000000015371315410276000314730ustar00rootroot00000000000000// +build linux package fs import ( "strconv" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type NetClsGroup struct { } func (s *NetClsGroup) Name() string { return "net_cls" } func (s *NetClsGroup) Apply(d *cgroupData) error { _, err := d.join("net_cls") if err != nil && !cgroups.IsNotFound(err) { return err } return nil } func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error { if cgroup.Resources.NetClsClassid != 0 { if err := writeFile(path, "net_cls.classid", strconv.FormatUint(uint64(cgroup.Resources.NetClsClassid), 10)); err != nil { return err } } return nil } func (s *NetClsGroup) Remove(d *cgroupData) error { return removePath(d.path("net_cls")) } func (s *NetClsGroup) GetStats(path string, stats *cgroups.Stats) error { return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go000066400000000000000000000015121315410276000316540ustar00rootroot00000000000000// +build linux package fs import ( "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type NetPrioGroup struct { } func (s *NetPrioGroup) Name() string { return "net_prio" } func (s *NetPrioGroup) Apply(d *cgroupData) error { _, err := d.join("net_prio") if err != nil && !cgroups.IsNotFound(err) { return err } return nil } func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error { for _, prioMap := range cgroup.Resources.NetPrioIfpriomap { if err := writeFile(path, "net_prio.ifpriomap", prioMap.CgroupString()); err != nil { return err } } return nil } func (s *NetPrioGroup) Remove(d *cgroupData) error { return removePath(d.path("net_prio")) } func (s *NetPrioGroup) GetStats(path string, stats *cgroups.Stats) error { return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/perf_event.go000066400000000000000000000013671315410276000322020ustar00rootroot00000000000000// +build linux package fs import ( "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type PerfEventGroup struct { } func (s *PerfEventGroup) Name() string { return "perf_event" } func (s *PerfEventGroup) Apply(d *cgroupData) error { // we just want to join this group even though we don't set anything if _, err := d.join("perf_event"); err != nil && !cgroups.IsNotFound(err) { return err } return nil } func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error { return nil } func (s *PerfEventGroup) Remove(d *cgroupData) error { return removePath(d.path("perf_event")) } func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error { return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go000066400000000000000000000031231315410276000307740ustar00rootroot00000000000000// +build linux package fs import ( "fmt" "path/filepath" "strconv" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type PidsGroup struct { } func (s *PidsGroup) Name() string { return "pids" } func (s *PidsGroup) Apply(d *cgroupData) error { _, err := d.join("pids") if err != nil && !cgroups.IsNotFound(err) { return err } return nil } func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error { if cgroup.Resources.PidsLimit != 0 { // "max" is the fallback value. limit := "max" if cgroup.Resources.PidsLimit > 0 { limit = strconv.FormatInt(cgroup.Resources.PidsLimit, 10) } if err := writeFile(path, "pids.max", limit); err != nil { return err } } return nil } func (s *PidsGroup) Remove(d *cgroupData) error { return removePath(d.path("pids")) } func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error { current, err := getCgroupParamUint(path, "pids.current") if err != nil { return fmt.Errorf("failed to parse pids.current - %s", err) } maxString, err := getCgroupParamString(path, "pids.max") if err != nil { return fmt.Errorf("failed to parse pids.max - %s", err) } // Default if pids.max == "max" is 0 -- which represents "no limit". var max uint64 if maxString != "max" { max, err = parseUint(maxString, 10, 64) if err != nil { return fmt.Errorf("failed to parse pids.max - unable to parse %q as a uint from Cgroup file %q", maxString, filepath.Join(path, "pids.max")) } } stats.PidsStats.Current = current stats.PidsStats.Limit = max return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils.go000066400000000000000000000040621315410276000312000ustar00rootroot00000000000000// +build linux package fs import ( "errors" "fmt" "io/ioutil" "path/filepath" "strconv" "strings" ) var ( ErrNotValidFormat = errors.New("line is not a valid key value format") ) // Saturates negative values at zero and returns a uint64. // Due to kernel bugs, some of the memory cgroup stats can be negative. func parseUint(s string, base, bitSize int) (uint64, error) { value, err := strconv.ParseUint(s, base, bitSize) if err != nil { intValue, intErr := strconv.ParseInt(s, base, bitSize) // 1. Handle negative values greater than MinInt64 (and) // 2. Handle negative values lesser than MinInt64 if intErr == nil && intValue < 0 { return 0, nil } else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 { return 0, nil } return value, err } return value, nil } // Parses a cgroup param and returns as name, value // i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234 func getCgroupParamKeyValue(t string) (string, uint64, error) { parts := strings.Fields(t) switch len(parts) { case 2: value, err := parseUint(parts[1], 10, 64) if err != nil { return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err) } return parts[0], value, nil default: return "", 0, ErrNotValidFormat } } // Gets a single uint64 value from the specified cgroup file. func getCgroupParamUint(cgroupPath, cgroupFile string) (uint64, error) { fileName := filepath.Join(cgroupPath, cgroupFile) contents, err := ioutil.ReadFile(fileName) if err != nil { return 0, err } res, err := parseUint(strings.TrimSpace(string(contents)), 10, 64) if err != nil { return res, fmt.Errorf("unable to parse %q as a uint from Cgroup file %q", string(contents), fileName) } return res, nil } // Gets a string value from the specified cgroup file func getCgroupParamString(cgroupPath, cgroupFile string) (string, error) { contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile)) if err != nil { return "", err } return strings.TrimSpace(string(contents)), nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/000077500000000000000000000000001315410276000307515ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/rootless/rootless.go000066400000000000000000000070641315410276000331610ustar00rootroot00000000000000// +build linux package rootless import ( "fmt" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/cgroups/fs" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs/validate" ) // TODO: This is copied from libcontainer/cgroups/fs, which duplicates this code // needlessly. We should probably export this list. var subsystems = []subsystem{ &fs.CpusetGroup{}, &fs.DevicesGroup{}, &fs.MemoryGroup{}, &fs.CpuGroup{}, &fs.CpuacctGroup{}, &fs.PidsGroup{}, &fs.BlkioGroup{}, &fs.HugetlbGroup{}, &fs.NetClsGroup{}, &fs.NetPrioGroup{}, &fs.PerfEventGroup{}, &fs.FreezerGroup{}, &fs.NameGroup{GroupName: "name=systemd"}, } type subsystem interface { // Name returns the name of the subsystem. Name() string // Returns the stats, as 'stats', corresponding to the cgroup under 'path'. GetStats(path string, stats *cgroups.Stats) error } // The noop cgroup manager is used for rootless containers, because we currently // cannot manage cgroups if we are in a rootless setup. This manager is chosen // by factory if we are in rootless mode. We error out if any cgroup options are // set in the config -- this may change in the future with upcoming kernel features // like the cgroup namespace. type Manager struct { Cgroups *configs.Cgroup Paths map[string]string } func (m *Manager) Apply(pid int) error { // If there are no cgroup settings, there's nothing to do. if m.Cgroups == nil { return nil } // We can't set paths. // TODO(cyphar): Implement the case where the runner of a rootless container // owns their own cgroup, which would allow us to set up a // cgroup for each path. if m.Cgroups.Paths != nil { return fmt.Errorf("cannot change cgroup path in rootless container") } // We load the paths into the manager. paths := make(map[string]string) for _, sys := range subsystems { name := sys.Name() path, err := cgroups.GetOwnCgroupPath(name) if err != nil { // Ignore paths we couldn't resolve. continue } paths[name] = path } m.Paths = paths return nil } func (m *Manager) GetPaths() map[string]string { return m.Paths } func (m *Manager) Set(container *configs.Config) error { // We have to re-do the validation here, since someone might decide to // update a rootless container. return validate.New().Validate(container) } func (m *Manager) GetPids() ([]int, error) { dir, err := cgroups.GetOwnCgroupPath("devices") if err != nil { return nil, err } return cgroups.GetPids(dir) } func (m *Manager) GetAllPids() ([]int, error) { dir, err := cgroups.GetOwnCgroupPath("devices") if err != nil { return nil, err } return cgroups.GetAllPids(dir) } func (m *Manager) GetStats() (*cgroups.Stats, error) { // TODO(cyphar): We can make this work if we figure out a way to allow usage // of cgroups with a rootless container. While this doesn't // actually require write access to a cgroup directory, the // statistics are not useful if they can be affected by // non-container processes. return nil, fmt.Errorf("cannot get cgroup stats in rootless container") } func (m *Manager) Freeze(state configs.FreezerState) error { // TODO(cyphar): We can make this work if we figure out a way to allow usage // of cgroups with a rootless container. return fmt.Errorf("cannot use freezer cgroup in rootless container") } func (m *Manager) Destroy() error { // We don't have to do anything here because we didn't do any setup. return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go000066400000000000000000000074711315410276000305750ustar00rootroot00000000000000// +build linux package cgroups type ThrottlingData struct { // Number of periods with throttling active Periods uint64 `json:"periods,omitempty"` // Number of periods when the container hit its throttling limit. ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` // Aggregate time the container was throttled for in nanoseconds. ThrottledTime uint64 `json:"throttled_time,omitempty"` } // CpuUsage denotes the usage of a CPU. // All CPU stats are aggregate since container inception. type CpuUsage struct { // Total CPU time consumed. // Units: nanoseconds. TotalUsage uint64 `json:"total_usage,omitempty"` // Total CPU time consumed per core. // Units: nanoseconds. PercpuUsage []uint64 `json:"percpu_usage,omitempty"` // Time spent by tasks of the cgroup in kernel mode. // Units: nanoseconds. UsageInKernelmode uint64 `json:"usage_in_kernelmode"` // Time spent by tasks of the cgroup in user mode. // Units: nanoseconds. UsageInUsermode uint64 `json:"usage_in_usermode"` } type CpuStats struct { CpuUsage CpuUsage `json:"cpu_usage,omitempty"` ThrottlingData ThrottlingData `json:"throttling_data,omitempty"` } type MemoryData struct { Usage uint64 `json:"usage,omitempty"` MaxUsage uint64 `json:"max_usage,omitempty"` Failcnt uint64 `json:"failcnt"` Limit uint64 `json:"limit"` } type MemoryStats struct { // memory used for cache Cache uint64 `json:"cache,omitempty"` // usage of memory Usage MemoryData `json:"usage,omitempty"` // usage of memory + swap SwapUsage MemoryData `json:"swap_usage,omitempty"` // usage of kernel memory KernelUsage MemoryData `json:"kernel_usage,omitempty"` // usage of kernel TCP memory KernelTCPUsage MemoryData `json:"kernel_tcp_usage,omitempty"` // if true, memory usage is accounted for throughout a hierarchy of cgroups. UseHierarchy bool `json:"use_hierarchy"` Stats map[string]uint64 `json:"stats,omitempty"` } type PidsStats struct { // number of pids in the cgroup Current uint64 `json:"current,omitempty"` // active pids hard limit Limit uint64 `json:"limit,omitempty"` } type BlkioStatEntry struct { Major uint64 `json:"major,omitempty"` Minor uint64 `json:"minor,omitempty"` Op string `json:"op,omitempty"` Value uint64 `json:"value,omitempty"` } type BlkioStats struct { // number of bytes tranferred to and from the block device IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"` IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive,omitempty"` IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"` IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive,omitempty"` IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive,omitempty"` IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive,omitempty"` IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive,omitempty"` SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"` } type HugetlbStats struct { // current res_counter usage for hugetlb Usage uint64 `json:"usage,omitempty"` // maximum usage ever recorded. MaxUsage uint64 `json:"max_usage,omitempty"` // number of times hugetlb usage allocation failure. Failcnt uint64 `json:"failcnt"` } type Stats struct { CpuStats CpuStats `json:"cpu_stats,omitempty"` MemoryStats MemoryStats `json:"memory_stats,omitempty"` PidsStats PidsStats `json:"pids_stats,omitempty"` BlkioStats BlkioStats `json:"blkio_stats,omitempty"` // the map is in the format "size of hugepage: stats of the hugepage" HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"` } func NewStats() *Stats { memoryStats := MemoryStats{Stats: make(map[string]uint64)} hugetlbStats := make(map[string]HugetlbStats) return &Stats{MemoryStats: memoryStats, HugetlbStats: hugetlbStats} } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/000077500000000000000000000000001315410276000305675ustar00rootroot00000000000000apply_nosystemd.go000066400000000000000000000022011315410276000342640ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd// +build !linux package systemd import ( "fmt" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" ) type Manager struct { Cgroups *configs.Cgroup Paths map[string]string } func UseSystemd() bool { return false } func (m *Manager) Apply(pid int) error { return fmt.Errorf("Systemd not supported") } func (m *Manager) GetPids() ([]int, error) { return nil, fmt.Errorf("Systemd not supported") } func (m *Manager) GetAllPids() ([]int, error) { return nil, fmt.Errorf("Systemd not supported") } func (m *Manager) Destroy() error { return fmt.Errorf("Systemd not supported") } func (m *Manager) GetPaths() map[string]string { return nil } func (m *Manager) GetStats() (*cgroups.Stats, error) { return nil, fmt.Errorf("Systemd not supported") } func (m *Manager) Set(container *configs.Config) error { return nil, fmt.Errorf("Systemd not supported") } func (m *Manager) Freeze(state configs.FreezerState) error { return fmt.Errorf("Systemd not supported") } func Freeze(c *configs.Cgroup, state configs.FreezerState) error { return fmt.Errorf("Systemd not supported") } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go000066400000000000000000000346031315410276000340210ustar00rootroot00000000000000// +build linux package systemd import ( "errors" "fmt" "os" "path/filepath" "strings" "sync" "time" systemdDbus "github.com/coreos/go-systemd/dbus" systemdUtil "github.com/coreos/go-systemd/util" "github.com/godbus/dbus" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/cgroups/fs" "github.com/opencontainers/runc/libcontainer/configs" ) type Manager struct { mu sync.Mutex Cgroups *configs.Cgroup Paths map[string]string } type subsystem interface { // Name returns the name of the subsystem. Name() string // Returns the stats, as 'stats', corresponding to the cgroup under 'path'. GetStats(path string, stats *cgroups.Stats) error // Set the cgroup represented by cgroup. Set(path string, cgroup *configs.Cgroup) error } var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist") type subsystemSet []subsystem func (s subsystemSet) Get(name string) (subsystem, error) { for _, ss := range s { if ss.Name() == name { return ss, nil } } return nil, errSubsystemDoesNotExist } var subsystems = subsystemSet{ &fs.CpusetGroup{}, &fs.DevicesGroup{}, &fs.MemoryGroup{}, &fs.CpuGroup{}, &fs.CpuacctGroup{}, &fs.PidsGroup{}, &fs.BlkioGroup{}, &fs.HugetlbGroup{}, &fs.PerfEventGroup{}, &fs.FreezerGroup{}, &fs.NetPrioGroup{}, &fs.NetClsGroup{}, &fs.NameGroup{GroupName: "name=systemd"}, } const ( testScopeWait = 4 testSliceWait = 4 ) var ( connLock sync.Mutex theConn *systemdDbus.Conn hasStartTransientUnit bool hasStartTransientSliceUnit bool hasTransientDefaultDependencies bool hasDelegate bool ) func newProp(name string, units interface{}) systemdDbus.Property { return systemdDbus.Property{ Name: name, Value: dbus.MakeVariant(units), } } func UseSystemd() bool { if !systemdUtil.IsRunningSystemd() { return false } connLock.Lock() defer connLock.Unlock() if theConn == nil { var err error theConn, err = systemdDbus.New() if err != nil { return false } // Assume we have StartTransientUnit hasStartTransientUnit = true // But if we get UnknownMethod error we don't if _, err := theConn.StartTransientUnit("test.scope", "invalid", nil, nil); err != nil { if dbusError, ok := err.(dbus.Error); ok { if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" { hasStartTransientUnit = false return hasStartTransientUnit } } } // Ensure the scope name we use doesn't exist. Use the Pid to // avoid collisions between multiple libcontainer users on a // single host. scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid()) testScopeExists := true for i := 0; i <= testScopeWait; i++ { if _, err := theConn.StopUnit(scope, "replace", nil); err != nil { if dbusError, ok := err.(dbus.Error); ok { if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") { testScopeExists = false break } } } time.Sleep(time.Millisecond) } // Bail out if we can't kill this scope without testing for DefaultDependencies if testScopeExists { return hasStartTransientUnit } // Assume StartTransientUnit on a scope allows DefaultDependencies hasTransientDefaultDependencies = true ddf := newProp("DefaultDependencies", false) if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{ddf}, nil); err != nil { if dbusError, ok := err.(dbus.Error); ok { if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") { hasTransientDefaultDependencies = false } } } // Not critical because of the stop unit logic above. theConn.StopUnit(scope, "replace", nil) // Assume StartTransientUnit on a scope allows Delegate hasDelegate = true dl := newProp("Delegate", true) if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{dl}, nil); err != nil { if dbusError, ok := err.(dbus.Error); ok { if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") { hasDelegate = false } } } // Assume we have the ability to start a transient unit as a slice // This was broken until systemd v229, but has been back-ported on RHEL environments >= 219 // For details, see: https://bugzilla.redhat.com/show_bug.cgi?id=1370299 hasStartTransientSliceUnit = true // To ensure simple clean-up, we create a slice off the root with no hierarchy slice := fmt.Sprintf("libcontainer_%d_systemd_test_default.slice", os.Getpid()) if _, err := theConn.StartTransientUnit(slice, "replace", nil, nil); err != nil { if _, ok := err.(dbus.Error); ok { hasStartTransientSliceUnit = false } } for i := 0; i <= testSliceWait; i++ { if _, err := theConn.StopUnit(slice, "replace", nil); err != nil { if dbusError, ok := err.(dbus.Error); ok { if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") { hasStartTransientSliceUnit = false break } } } else { break } time.Sleep(time.Millisecond) } // Not critical because of the stop unit logic above. theConn.StopUnit(scope, "replace", nil) theConn.StopUnit(slice, "replace", nil) } return hasStartTransientUnit } func (m *Manager) Apply(pid int) error { var ( c = m.Cgroups unitName = getUnitName(c) slice = "system.slice" properties []systemdDbus.Property ) if c.Paths != nil { paths := make(map[string]string) for name, path := range c.Paths { _, err := getSubsystemPath(m.Cgroups, name) if err != nil { // Don't fail if a cgroup hierarchy was not found, just skip this subsystem if cgroups.IsNotFound(err) { continue } return err } paths[name] = path } m.Paths = paths return cgroups.EnterPid(m.Paths, pid) } if c.Parent != "" { slice = c.Parent } properties = append(properties, systemdDbus.PropDescription("libcontainer container "+c.Name)) // if we create a slice, the parent is defined via a Wants= if strings.HasSuffix(unitName, ".slice") { // This was broken until systemd v229, but has been back-ported on RHEL environments >= 219 if !hasStartTransientSliceUnit { return fmt.Errorf("systemd version does not support ability to start a slice as transient unit") } properties = append(properties, systemdDbus.PropWants(slice)) } else { // otherwise, we use Slice= properties = append(properties, systemdDbus.PropSlice(slice)) } // only add pid if its valid, -1 is used w/ general slice creation. if pid != -1 { properties = append(properties, newProp("PIDs", []uint32{uint32(pid)})) } if hasDelegate { // This is only supported on systemd versions 218 and above. properties = append(properties, newProp("Delegate", true)) } // Always enable accounting, this gets us the same behaviour as the fs implementation, // plus the kernel has some problems with joining the memory cgroup at a later time. properties = append(properties, newProp("MemoryAccounting", true), newProp("CPUAccounting", true), newProp("BlockIOAccounting", true)) if hasTransientDefaultDependencies { properties = append(properties, newProp("DefaultDependencies", false)) } if c.Resources.Memory != 0 { properties = append(properties, newProp("MemoryLimit", uint64(c.Resources.Memory))) } if c.Resources.CpuShares != 0 { properties = append(properties, newProp("CPUShares", c.Resources.CpuShares)) } // cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd. if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 { cpuQuotaPerSecUSec := uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod properties = append(properties, newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec)) } if c.Resources.BlkioWeight != 0 { properties = append(properties, newProp("BlockIOWeight", uint64(c.Resources.BlkioWeight))) } // We have to set kernel memory here, as we can't change it once // processes have been attached to the cgroup. if c.Resources.KernelMemory != 0 { if err := setKernelMemory(c); err != nil { return err } } if _, err := theConn.StartTransientUnit(unitName, "replace", properties, nil); err != nil && !isUnitExists(err) { return err } if err := joinCgroups(c, pid); err != nil { return err } paths := make(map[string]string) for _, s := range subsystems { subsystemPath, err := getSubsystemPath(m.Cgroups, s.Name()) if err != nil { // Don't fail if a cgroup hierarchy was not found, just skip this subsystem if cgroups.IsNotFound(err) { continue } return err } paths[s.Name()] = subsystemPath } m.Paths = paths return nil } func (m *Manager) Destroy() error { if m.Cgroups.Paths != nil { return nil } m.mu.Lock() defer m.mu.Unlock() theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil) if err := cgroups.RemovePaths(m.Paths); err != nil { return err } m.Paths = make(map[string]string) return nil } func (m *Manager) GetPaths() map[string]string { m.mu.Lock() paths := m.Paths m.mu.Unlock() return paths } func join(c *configs.Cgroup, subsystem string, pid int) (string, error) { path, err := getSubsystemPath(c, subsystem) if err != nil { return "", err } if err := os.MkdirAll(path, 0755); err != nil { return "", err } if err := cgroups.WriteCgroupProc(path, pid); err != nil { return "", err } return path, nil } func joinCgroups(c *configs.Cgroup, pid int) error { for _, sys := range subsystems { name := sys.Name() switch name { case "name=systemd": // let systemd handle this case "cpuset": path, err := getSubsystemPath(c, name) if err != nil && !cgroups.IsNotFound(err) { return err } s := &fs.CpusetGroup{} if err := s.ApplyDir(path, c, pid); err != nil { return err } default: _, err := join(c, name, pid) if err != nil { // Even if it's `not found` error, we'll return err // because devices cgroup is hard requirement for // container security. if name == "devices" { return err } // For other subsystems, omit the `not found` error // because they are optional. if !cgroups.IsNotFound(err) { return err } } } } return nil } // systemd represents slice hierarchy using `-`, so we need to follow suit when // generating the path of slice. Essentially, test-a-b.slice becomes // test.slice/test-a.slice/test-a-b.slice. func ExpandSlice(slice string) (string, error) { suffix := ".slice" // Name has to end with ".slice", but can't be just ".slice". if len(slice) < len(suffix) || !strings.HasSuffix(slice, suffix) { return "", fmt.Errorf("invalid slice name: %s", slice) } // Path-separators are not allowed. if strings.Contains(slice, "/") { return "", fmt.Errorf("invalid slice name: %s", slice) } var path, prefix string sliceName := strings.TrimSuffix(slice, suffix) // if input was -.slice, we should just return root now if sliceName == "-" { return "/", nil } for _, component := range strings.Split(sliceName, "-") { // test--a.slice isn't permitted, nor is -test.slice. if component == "" { return "", fmt.Errorf("invalid slice name: %s", slice) } // Append the component to the path and to the prefix. path += prefix + component + suffix + "/" prefix += component + "-" } return path, nil } func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) { mountpoint, err := cgroups.FindCgroupMountpoint(subsystem) if err != nil { return "", err } initPath, err := cgroups.GetInitCgroup(subsystem) if err != nil { return "", err } // if pid 1 is systemd 226 or later, it will be in init.scope, not the root initPath = strings.TrimSuffix(filepath.Clean(initPath), "init.scope") slice := "system.slice" if c.Parent != "" { slice = c.Parent } slice, err = ExpandSlice(slice) if err != nil { return "", err } return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil } func (m *Manager) Freeze(state configs.FreezerState) error { path, err := getSubsystemPath(m.Cgroups, "freezer") if err != nil { return err } prevState := m.Cgroups.Resources.Freezer m.Cgroups.Resources.Freezer = state freezer, err := subsystems.Get("freezer") if err != nil { return err } err = freezer.Set(path, m.Cgroups) if err != nil { m.Cgroups.Resources.Freezer = prevState return err } return nil } func (m *Manager) GetPids() ([]int, error) { path, err := getSubsystemPath(m.Cgroups, "devices") if err != nil { return nil, err } return cgroups.GetPids(path) } func (m *Manager) GetAllPids() ([]int, error) { path, err := getSubsystemPath(m.Cgroups, "devices") if err != nil { return nil, err } return cgroups.GetAllPids(path) } func (m *Manager) GetStats() (*cgroups.Stats, error) { m.mu.Lock() defer m.mu.Unlock() stats := cgroups.NewStats() for name, path := range m.Paths { sys, err := subsystems.Get(name) if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) { continue } if err := sys.GetStats(path, stats); err != nil { return nil, err } } return stats, nil } func (m *Manager) Set(container *configs.Config) error { // If Paths are set, then we are just joining cgroups paths // and there is no need to set any values. if m.Cgroups.Paths != nil { return nil } for _, sys := range subsystems { // Get the subsystem path, but don't error out for not found cgroups. path, err := getSubsystemPath(container.Cgroups, sys.Name()) if err != nil && !cgroups.IsNotFound(err) { return err } if err := sys.Set(path, container.Cgroups); err != nil { return err } } if m.Paths["cpu"] != "" { if err := fs.CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); err != nil { return err } } return nil } func getUnitName(c *configs.Cgroup) string { // by default, we create a scope unless the user explicitly asks for a slice. if !strings.HasSuffix(c.Name, ".slice") { return fmt.Sprintf("%s-%s.scope", c.ScopePrefix, c.Name) } return c.Name } func setKernelMemory(c *configs.Cgroup) error { path, err := getSubsystemPath(c, "memory") if err != nil && !cgroups.IsNotFound(err) { return err } if err := os.MkdirAll(path, 0755); err != nil { return err } return fs.EnableKernelMemoryAccounting(path) } // isUnitExists returns true if the error is that a systemd unit already exists. func isUnitExists(err error) bool { if err != nil { if dbusError, ok := err.(dbus.Error); ok { return strings.Contains(dbusError.Name, "org.freedesktop.systemd1.UnitExists") } } return false } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go000066400000000000000000000260261315410276000305740ustar00rootroot00000000000000// +build linux package cgroups import ( "bufio" "fmt" "io" "io/ioutil" "os" "path/filepath" "strconv" "strings" "time" "github.com/docker/go-units" ) const ( cgroupNamePrefix = "name=" CgroupProcesses = "cgroup.procs" ) // https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt func FindCgroupMountpoint(subsystem string) (string, error) { mnt, _, err := FindCgroupMountpointAndRoot(subsystem) return mnt, err } func FindCgroupMountpointAndRoot(subsystem string) (string, string, error) { // We are not using mount.GetMounts() because it's super-inefficient, // parsing it directly sped up x10 times because of not using Sscanf. // It was one of two major performance drawbacks in container start. if !isSubsystemAvailable(subsystem) { return "", "", NewNotFoundError(subsystem) } f, err := os.Open("/proc/self/mountinfo") if err != nil { return "", "", err } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { txt := scanner.Text() fields := strings.Split(txt, " ") for _, opt := range strings.Split(fields[len(fields)-1], ",") { if opt == subsystem { return fields[4], fields[3], nil } } } if err := scanner.Err(); err != nil { return "", "", err } return "", "", NewNotFoundError(subsystem) } func isSubsystemAvailable(subsystem string) bool { cgroups, err := ParseCgroupFile("/proc/self/cgroup") if err != nil { return false } _, avail := cgroups[subsystem] return avail } func GetClosestMountpointAncestor(dir, mountinfo string) string { deepestMountPoint := "" for _, mountInfoEntry := range strings.Split(mountinfo, "\n") { mountInfoParts := strings.Fields(mountInfoEntry) if len(mountInfoParts) < 5 { continue } mountPoint := mountInfoParts[4] if strings.HasPrefix(mountPoint, deepestMountPoint) && strings.HasPrefix(dir, mountPoint) { deepestMountPoint = mountPoint } } return deepestMountPoint } func FindCgroupMountpointDir() (string, error) { f, err := os.Open("/proc/self/mountinfo") if err != nil { return "", err } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { text := scanner.Text() fields := strings.Split(text, " ") // Safe as mountinfo encodes mountpoints with spaces as \040. index := strings.Index(text, " - ") postSeparatorFields := strings.Fields(text[index+3:]) numPostFields := len(postSeparatorFields) // This is an error as we can't detect if the mount is for "cgroup" if numPostFields == 0 { return "", fmt.Errorf("Found no fields post '-' in %q", text) } if postSeparatorFields[0] == "cgroup" { // Check that the mount is properly formated. if numPostFields < 3 { return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text) } return filepath.Dir(fields[4]), nil } } if err := scanner.Err(); err != nil { return "", err } return "", NewNotFoundError("cgroup") } type Mount struct { Mountpoint string Root string Subsystems []string } func (m Mount) GetOwnCgroup(cgroups map[string]string) (string, error) { if len(m.Subsystems) == 0 { return "", fmt.Errorf("no subsystem for mount") } return getControllerPath(m.Subsystems[0], cgroups) } func getCgroupMountsHelper(ss map[string]bool, mi io.Reader, all bool) ([]Mount, error) { res := make([]Mount, 0, len(ss)) scanner := bufio.NewScanner(mi) numFound := 0 for scanner.Scan() && numFound < len(ss) { txt := scanner.Text() sepIdx := strings.Index(txt, " - ") if sepIdx == -1 { return nil, fmt.Errorf("invalid mountinfo format") } if txt[sepIdx+3:sepIdx+10] == "cgroup2" || txt[sepIdx+3:sepIdx+9] != "cgroup" { continue } fields := strings.Split(txt, " ") m := Mount{ Mountpoint: fields[4], Root: fields[3], } for _, opt := range strings.Split(fields[len(fields)-1], ",") { if !ss[opt] { continue } if strings.HasPrefix(opt, cgroupNamePrefix) { m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):]) } else { m.Subsystems = append(m.Subsystems, opt) } if !all { numFound++ } } res = append(res, m) } if err := scanner.Err(); err != nil { return nil, err } return res, nil } // GetCgroupMounts returns the mounts for the cgroup subsystems. // all indicates whether to return just the first instance or all the mounts. func GetCgroupMounts(all bool) ([]Mount, error) { f, err := os.Open("/proc/self/mountinfo") if err != nil { return nil, err } defer f.Close() allSubsystems, err := ParseCgroupFile("/proc/self/cgroup") if err != nil { return nil, err } allMap := make(map[string]bool) for s := range allSubsystems { allMap[s] = true } return getCgroupMountsHelper(allMap, f, all) } // GetAllSubsystems returns all the cgroup subsystems supported by the kernel func GetAllSubsystems() ([]string, error) { f, err := os.Open("/proc/cgroups") if err != nil { return nil, err } defer f.Close() subsystems := []string{} s := bufio.NewScanner(f) for s.Scan() { text := s.Text() if text[0] != '#' { parts := strings.Fields(text) if len(parts) >= 4 && parts[3] != "0" { subsystems = append(subsystems, parts[0]) } } } if err := s.Err(); err != nil { return nil, err } return subsystems, nil } // GetOwnCgroup returns the relative path to the cgroup docker is running in. func GetOwnCgroup(subsystem string) (string, error) { cgroups, err := ParseCgroupFile("/proc/self/cgroup") if err != nil { return "", err } return getControllerPath(subsystem, cgroups) } func GetOwnCgroupPath(subsystem string) (string, error) { cgroup, err := GetOwnCgroup(subsystem) if err != nil { return "", err } return getCgroupPathHelper(subsystem, cgroup) } func GetInitCgroup(subsystem string) (string, error) { cgroups, err := ParseCgroupFile("/proc/1/cgroup") if err != nil { return "", err } return getControllerPath(subsystem, cgroups) } func GetInitCgroupPath(subsystem string) (string, error) { cgroup, err := GetInitCgroup(subsystem) if err != nil { return "", err } return getCgroupPathHelper(subsystem, cgroup) } func getCgroupPathHelper(subsystem, cgroup string) (string, error) { mnt, root, err := FindCgroupMountpointAndRoot(subsystem) if err != nil { return "", err } // This is needed for nested containers, because in /proc/self/cgroup we // see pathes from host, which don't exist in container. relCgroup, err := filepath.Rel(root, cgroup) if err != nil { return "", err } return filepath.Join(mnt, relCgroup), nil } func readProcsFile(dir string) ([]int, error) { f, err := os.Open(filepath.Join(dir, CgroupProcesses)) if err != nil { return nil, err } defer f.Close() var ( s = bufio.NewScanner(f) out = []int{} ) for s.Scan() { if t := s.Text(); t != "" { pid, err := strconv.Atoi(t) if err != nil { return nil, err } out = append(out, pid) } } return out, nil } // ParseCgroupFile parses the given cgroup file, typically from // /proc//cgroup, into a map of subgroups to cgroup names. func ParseCgroupFile(path string) (map[string]string, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() return parseCgroupFromReader(f) } // helper function for ParseCgroupFile to make testing easier func parseCgroupFromReader(r io.Reader) (map[string]string, error) { s := bufio.NewScanner(r) cgroups := make(map[string]string) for s.Scan() { text := s.Text() // from cgroups(7): // /proc/[pid]/cgroup // ... // For each cgroup hierarchy ... there is one entry // containing three colon-separated fields of the form: // hierarchy-ID:subsystem-list:cgroup-path parts := strings.SplitN(text, ":", 3) if len(parts) < 3 { return nil, fmt.Errorf("invalid cgroup entry: must contain at least two colons: %v", text) } for _, subs := range strings.Split(parts[1], ",") { cgroups[subs] = parts[2] } } if err := s.Err(); err != nil { return nil, err } return cgroups, nil } func getControllerPath(subsystem string, cgroups map[string]string) (string, error) { if p, ok := cgroups[subsystem]; ok { return p, nil } if p, ok := cgroups[cgroupNamePrefix+subsystem]; ok { return p, nil } return "", NewNotFoundError(subsystem) } func PathExists(path string) bool { if _, err := os.Stat(path); err != nil { return false } return true } func EnterPid(cgroupPaths map[string]string, pid int) error { for _, path := range cgroupPaths { if PathExists(path) { if err := WriteCgroupProc(path, pid); err != nil { return err } } } return nil } // RemovePaths iterates over the provided paths removing them. // We trying to remove all paths five times with increasing delay between tries. // If after all there are not removed cgroups - appropriate error will be // returned. func RemovePaths(paths map[string]string) (err error) { delay := 10 * time.Millisecond for i := 0; i < 5; i++ { if i != 0 { time.Sleep(delay) delay *= 2 } for s, p := range paths { os.RemoveAll(p) // TODO: here probably should be logging _, err := os.Stat(p) // We need this strange way of checking cgroups existence because // RemoveAll almost always returns error, even on already removed // cgroups if os.IsNotExist(err) { delete(paths, s) } } if len(paths) == 0 { return nil } } return fmt.Errorf("Failed to remove paths: %v", paths) } func GetHugePageSize() ([]string, error) { var pageSizes []string sizeList := []string{"B", "kB", "MB", "GB", "TB", "PB"} files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages") if err != nil { return pageSizes, err } for _, st := range files { nameArray := strings.Split(st.Name(), "-") pageSize, err := units.RAMInBytes(nameArray[1]) if err != nil { return []string{}, err } sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList) pageSizes = append(pageSizes, sizeString) } return pageSizes, nil } // GetPids returns all pids, that were added to cgroup at path. func GetPids(path string) ([]int, error) { return readProcsFile(path) } // GetAllPids returns all pids, that were added to cgroup at path and to all its // subcgroups. func GetAllPids(path string) ([]int, error) { var pids []int // collect pids from all sub-cgroups err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error { dir, file := filepath.Split(p) if file != CgroupProcesses { return nil } if iErr != nil { return iErr } cPids, err := readProcsFile(dir) if err != nil { return err } pids = append(pids, cPids...) return nil }) return pids, err } // WriteCgroupProc writes the specified pid into the cgroup's cgroup.procs file func WriteCgroupProc(dir string, pid int) error { // Normally dir should not be empty, one case is that cgroup subsystem // is not mounted, we will get empty dir, and we want it fail here. if dir == "" { return fmt.Errorf("no such directory for %s", CgroupProcesses) } // Dont attach any pid to the cgroup if -1 is specified as a pid if pid != -1 { if err := ioutil.WriteFile(filepath.Join(dir, CgroupProcesses), []byte(strconv.Itoa(pid)), 0700); err != nil { return fmt.Errorf("failed to write %v to %v: %v", pid, CgroupProcesses, err) } } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/compat_1.5_linux.go000066400000000000000000000003331315410276000310300ustar00rootroot00000000000000// +build linux,!go1.5 package libcontainer import "syscall" // GidMappingsEnableSetgroups was added in Go 1.5, so do nothing when building // with earlier versions func enableSetgroups(sys *syscall.SysProcAttr) { } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/000077500000000000000000000000001315410276000270455ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go000066400000000000000000000036331315410276000320200ustar00rootroot00000000000000package configs import "fmt" // blockIODevice holds major:minor format supported in blkio cgroup type blockIODevice struct { // Major is the device's major number Major int64 `json:"major"` // Minor is the device's minor number Minor int64 `json:"minor"` } // WeightDevice struct holds a `major:minor weight`|`major:minor leaf_weight` pair type WeightDevice struct { blockIODevice // Weight is the bandwidth rate for the device, range is from 10 to 1000 Weight uint16 `json:"weight"` // LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only LeafWeight uint16 `json:"leafWeight"` } // NewWeightDevice returns a configured WeightDevice pointer func NewWeightDevice(major, minor int64, weight, leafWeight uint16) *WeightDevice { wd := &WeightDevice{} wd.Major = major wd.Minor = minor wd.Weight = weight wd.LeafWeight = leafWeight return wd } // WeightString formats the struct to be writable to the cgroup specific file func (wd *WeightDevice) WeightString() string { return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.Weight) } // LeafWeightString formats the struct to be writable to the cgroup specific file func (wd *WeightDevice) LeafWeightString() string { return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.LeafWeight) } // ThrottleDevice struct holds a `major:minor rate_per_second` pair type ThrottleDevice struct { blockIODevice // Rate is the IO rate limit per cgroup per device Rate uint64 `json:"rate"` } // NewThrottleDevice returns a configured ThrottleDevice pointer func NewThrottleDevice(major, minor int64, rate uint64) *ThrottleDevice { td := &ThrottleDevice{} td.Major = major td.Minor = minor td.Rate = rate return td } // String formats the struct to be writable to the cgroup specific file func (td *ThrottleDevice) String() string { return fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_linux.go000066400000000000000000000077351315410276000321260ustar00rootroot00000000000000package configs type FreezerState string const ( Undefined FreezerState = "" Frozen FreezerState = "FROZEN" Thawed FreezerState = "THAWED" ) type Cgroup struct { // Deprecated, use Path instead Name string `json:"name,omitempty"` // name of parent of cgroup or slice // Deprecated, use Path instead Parent string `json:"parent,omitempty"` // Path specifies the path to cgroups that are created and/or joined by the container. // The path is assumed to be relative to the host system cgroup mountpoint. Path string `json:"path"` // ScopePrefix describes prefix for the scope name ScopePrefix string `json:"scope_prefix"` // Paths represent the absolute cgroups paths to join. // This takes precedence over Path. Paths map[string]string // Resources contains various cgroups settings to apply *Resources } type Resources struct { // If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list. // Deprecated AllowAllDevices *bool `json:"allow_all_devices,omitempty"` // Deprecated AllowedDevices []*Device `json:"allowed_devices,omitempty"` // Deprecated DeniedDevices []*Device `json:"denied_devices,omitempty"` Devices []*Device `json:"devices"` // Memory limit (in bytes) Memory int64 `json:"memory"` // Memory reservation or soft_limit (in bytes) MemoryReservation int64 `json:"memory_reservation"` // Total memory usage (memory + swap); set `-1` to enable unlimited swap MemorySwap int64 `json:"memory_swap"` // Kernel memory limit (in bytes) KernelMemory int64 `json:"kernel_memory"` // Kernel memory limit for TCP use (in bytes) KernelMemoryTCP int64 `json:"kernel_memory_tcp"` // CPU shares (relative weight vs. other containers) CpuShares uint64 `json:"cpu_shares"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. CpuQuota int64 `json:"cpu_quota"` // CPU period to be used for hardcapping (in usecs). 0 to use system default. CpuPeriod uint64 `json:"cpu_period"` // How many time CPU will use in realtime scheduling (in usecs). CpuRtRuntime int64 `json:"cpu_rt_quota"` // CPU period to be used for realtime scheduling (in usecs). CpuRtPeriod uint64 `json:"cpu_rt_period"` // CPU to use CpusetCpus string `json:"cpuset_cpus"` // MEM to use CpusetMems string `json:"cpuset_mems"` // Process limit; set <= `0' to disable limit. PidsLimit int64 `json:"pids_limit"` // Specifies per cgroup weight, range is from 10 to 1000. BlkioWeight uint16 `json:"blkio_weight"` // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only BlkioLeafWeight uint16 `json:"blkio_leaf_weight"` // Weight per cgroup per device, can override BlkioWeight. BlkioWeightDevice []*WeightDevice `json:"blkio_weight_device"` // IO read rate limit per cgroup per device, bytes per second. BlkioThrottleReadBpsDevice []*ThrottleDevice `json:"blkio_throttle_read_bps_device"` // IO write rate limit per cgroup per device, bytes per second. BlkioThrottleWriteBpsDevice []*ThrottleDevice `json:"blkio_throttle_write_bps_device"` // IO read rate limit per cgroup per device, IO per second. BlkioThrottleReadIOPSDevice []*ThrottleDevice `json:"blkio_throttle_read_iops_device"` // IO write rate limit per cgroup per device, IO per second. BlkioThrottleWriteIOPSDevice []*ThrottleDevice `json:"blkio_throttle_write_iops_device"` // set the freeze value for the process Freezer FreezerState `json:"freezer"` // Hugetlb limit (in bytes) HugetlbLimit []*HugepageLimit `json:"hugetlb_limit"` // Whether to disable OOM Killer OomKillDisable bool `json:"oom_kill_disable"` // Tuning swappiness behaviour per cgroup MemorySwappiness *uint64 `json:"memory_swappiness"` // Set priority of network traffic for container NetPrioIfpriomap []*IfPrioMap `json:"net_prio_ifpriomap"` // Set class identifier for container's network packets NetClsClassid uint32 `json:"net_cls_classid_u"` } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go000066400000000000000000000001141315410276000333370ustar00rootroot00000000000000// +build !windows,!linux,!freebsd package configs type Cgroup struct { } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_windows.go000066400000000000000000000002361315410276000324460ustar00rootroot00000000000000package configs // TODO Windows: This can ultimately be entirely factored out on Windows as // cgroups are a Unix-specific construct. type Cgroup struct { } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go000066400000000000000000000245411315410276000306470ustar00rootroot00000000000000package configs import ( "bytes" "encoding/json" "fmt" "os/exec" "time" "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) type Rlimit struct { Type int `json:"type"` Hard uint64 `json:"hard"` Soft uint64 `json:"soft"` } // IDMap represents UID/GID Mappings for User Namespaces. type IDMap struct { ContainerID int `json:"container_id"` HostID int `json:"host_id"` Size int `json:"size"` } // Seccomp represents syscall restrictions // By default, only the native architecture of the kernel is allowed to be used // for syscalls. Additional architectures can be added by specifying them in // Architectures. type Seccomp struct { DefaultAction Action `json:"default_action"` Architectures []string `json:"architectures"` Syscalls []*Syscall `json:"syscalls"` } // Action is taken upon rule match in Seccomp type Action int const ( Kill Action = iota + 1 Errno Trap Allow Trace ) // Operator is a comparison operator to be used when matching syscall arguments in Seccomp type Operator int const ( EqualTo Operator = iota + 1 NotEqualTo GreaterThan GreaterThanOrEqualTo LessThan LessThanOrEqualTo MaskEqualTo ) // Arg is a rule to match a specific syscall argument in Seccomp type Arg struct { Index uint `json:"index"` Value uint64 `json:"value"` ValueTwo uint64 `json:"value_two"` Op Operator `json:"op"` } // Syscall is a rule to match a syscall in Seccomp type Syscall struct { Name string `json:"name"` Action Action `json:"action"` Args []*Arg `json:"args"` } // TODO Windows. Many of these fields should be factored out into those parts // which are common across platforms, and those which are platform specific. // Config defines configuration options for executing a process inside a contained environment. type Config struct { // NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs // This is a common option when the container is running in ramdisk NoPivotRoot bool `json:"no_pivot_root"` // ParentDeathSignal specifies the signal that is sent to the container's process in the case // that the parent process dies. ParentDeathSignal int `json:"parent_death_signal"` // Path to a directory containing the container's root filesystem. Rootfs string `json:"rootfs"` // Readonlyfs will remount the container's rootfs as readonly where only externally mounted // bind mounts are writtable. Readonlyfs bool `json:"readonlyfs"` // Specifies the mount propagation flags to be applied to /. RootPropagation int `json:"rootPropagation"` // Mounts specify additional source and destination paths that will be mounted inside the container's // rootfs and mount namespace if specified Mounts []*Mount `json:"mounts"` // The device nodes that should be automatically created within the container upon container start. Note, make sure that the node is marked as allowed in the cgroup as well! Devices []*Device `json:"devices"` MountLabel string `json:"mount_label"` // Hostname optionally sets the container's hostname if provided Hostname string `json:"hostname"` // Namespaces specifies the container's namespaces that it should setup when cloning the init process // If a namespace is not provided that namespace is shared from the container's parent process Namespaces Namespaces `json:"namespaces"` // Capabilities specify the capabilities to keep when executing the process inside the container // All capabilities not specified will be dropped from the processes capability mask Capabilities *Capabilities `json:"capabilities"` // Networks specifies the container's network setup to be created Networks []*Network `json:"networks"` // Routes can be specified to create entries in the route table as the container is started Routes []*Route `json:"routes"` // Cgroups specifies specific cgroup settings for the various subsystems that the container is // placed into to limit the resources the container has available Cgroups *Cgroup `json:"cgroups"` // AppArmorProfile specifies the profile to apply to the process running in the container and is // change at the time the process is execed AppArmorProfile string `json:"apparmor_profile,omitempty"` // ProcessLabel specifies the label to apply to the process running in the container. It is // commonly used by selinux ProcessLabel string `json:"process_label,omitempty"` // Rlimits specifies the resource limits, such as max open files, to set in the container // If Rlimits are not set, the container will inherit rlimits from the parent process Rlimits []Rlimit `json:"rlimits,omitempty"` // OomScoreAdj specifies the adjustment to be made by the kernel when calculating oom scores // for a process. Valid values are between the range [-1000, '1000'], where processes with // higher scores are preferred for being killed. // More information about kernel oom score calculation here: https://lwn.net/Articles/317814/ OomScoreAdj int `json:"oom_score_adj"` // UidMappings is an array of User ID mappings for User Namespaces UidMappings []IDMap `json:"uid_mappings"` // GidMappings is an array of Group ID mappings for User Namespaces GidMappings []IDMap `json:"gid_mappings"` // MaskPaths specifies paths within the container's rootfs to mask over with a bind // mount pointing to /dev/null as to prevent reads of the file. MaskPaths []string `json:"mask_paths"` // ReadonlyPaths specifies paths within the container's rootfs to remount as read-only // so that these files prevent any writes. ReadonlyPaths []string `json:"readonly_paths"` // Sysctl is a map of properties and their values. It is the equivalent of using // sysctl -w my.property.name value in Linux. Sysctl map[string]string `json:"sysctl"` // Seccomp allows actions to be taken whenever a syscall is made within the container. // A number of rules are given, each having an action to be taken if a syscall matches it. // A default action to be taken if no rules match is also given. Seccomp *Seccomp `json:"seccomp"` // NoNewPrivileges controls whether processes in the container can gain additional privileges. NoNewPrivileges bool `json:"no_new_privileges,omitempty"` // Hooks are a collection of actions to perform at various container lifecycle events. // CommandHooks are serialized to JSON, but other hooks are not. Hooks *Hooks // Version is the version of opencontainer specification that is supported. Version string `json:"version"` // Labels are user defined metadata that is stored in the config and populated on the state Labels []string `json:"labels"` // NoNewKeyring will not allocated a new session keyring for the container. It will use the // callers keyring in this case. NoNewKeyring bool `json:"no_new_keyring"` // Rootless specifies whether the container is a rootless container. Rootless bool `json:"rootless"` } type Hooks struct { // Prestart commands are executed after the container namespaces are created, // but before the user supplied command is executed from init. Prestart []Hook // Poststart commands are executed after the container init process starts. Poststart []Hook // Poststop commands are executed after the container init process exits. Poststop []Hook } type Capabilities struct { // Bounding is the set of capabilities checked by the kernel. Bounding []string // Effective is the set of capabilities checked by the kernel. Effective []string // Inheritable is the capabilities preserved across execve. Inheritable []string // Permitted is the limiting superset for effective capabilities. Permitted []string // Ambient is the ambient set of capabilities that are kept. Ambient []string } func (hooks *Hooks) UnmarshalJSON(b []byte) error { var state struct { Prestart []CommandHook Poststart []CommandHook Poststop []CommandHook } if err := json.Unmarshal(b, &state); err != nil { return err } deserialize := func(shooks []CommandHook) (hooks []Hook) { for _, shook := range shooks { hooks = append(hooks, shook) } return hooks } hooks.Prestart = deserialize(state.Prestart) hooks.Poststart = deserialize(state.Poststart) hooks.Poststop = deserialize(state.Poststop) return nil } func (hooks Hooks) MarshalJSON() ([]byte, error) { serialize := func(hooks []Hook) (serializableHooks []CommandHook) { for _, hook := range hooks { switch chook := hook.(type) { case CommandHook: serializableHooks = append(serializableHooks, chook) default: logrus.Warnf("cannot serialize hook of type %T, skipping", hook) } } return serializableHooks } return json.Marshal(map[string]interface{}{ "prestart": serialize(hooks.Prestart), "poststart": serialize(hooks.Poststart), "poststop": serialize(hooks.Poststop), }) } // HookState is the payload provided to a hook on execution. type HookState specs.State type Hook interface { // Run executes the hook with the provided state. Run(HookState) error } // NewFunctionHook will call the provided function when the hook is run. func NewFunctionHook(f func(HookState) error) FuncHook { return FuncHook{ run: f, } } type FuncHook struct { run func(HookState) error } func (f FuncHook) Run(s HookState) error { return f.run(s) } type Command struct { Path string `json:"path"` Args []string `json:"args"` Env []string `json:"env"` Dir string `json:"dir"` Timeout *time.Duration `json:"timeout"` } // NewCommandHook will execute the provided command when the hook is run. func NewCommandHook(cmd Command) CommandHook { return CommandHook{ Command: cmd, } } type CommandHook struct { Command } func (c Command) Run(s HookState) error { b, err := json.Marshal(s) if err != nil { return err } var stdout, stderr bytes.Buffer cmd := exec.Cmd{ Path: c.Path, Args: c.Args, Env: c.Env, Stdin: bytes.NewReader(b), Stdout: &stdout, Stderr: &stderr, } if err := cmd.Start(); err != nil { return err } errC := make(chan error, 1) go func() { err := cmd.Wait() if err != nil { err = fmt.Errorf("error running hook: %v, stdout: %s, stderr: %s", err, stdout.String(), stderr.String()) } errC <- err }() var timerCh <-chan time.Time if c.Timeout != nil { timer := time.NewTimer(*c.Timeout) defer timer.Stop() timerCh = timer.C } select { case err := <-errC: return err case <-timerCh: cmd.Process.Kill() cmd.Wait() return fmt.Errorf("hook ran past specified timeout of %.1fs", c.Timeout.Seconds()) } } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/config_linux.go000066400000000000000000000036261315410276000320670ustar00rootroot00000000000000package configs import "fmt" // HostUID gets the translated uid for the process on host which could be // different when user namespaces are enabled. func (c Config) HostUID(containerId int) (int, error) { if c.Namespaces.Contains(NEWUSER) { if c.UidMappings == nil { return -1, fmt.Errorf("User namespaces enabled, but no uid mappings found.") } id, found := c.hostIDFromMapping(containerId, c.UidMappings) if !found { return -1, fmt.Errorf("User namespaces enabled, but no user mapping found.") } return id, nil } // Return unchanged id. return containerId, nil } // HostRootUID gets the root uid for the process on host which could be non-zero // when user namespaces are enabled. func (c Config) HostRootUID() (int, error) { return c.HostUID(0) } // HostGID gets the translated gid for the process on host which could be // different when user namespaces are enabled. func (c Config) HostGID(containerId int) (int, error) { if c.Namespaces.Contains(NEWUSER) { if c.GidMappings == nil { return -1, fmt.Errorf("User namespaces enabled, but no gid mappings found.") } id, found := c.hostIDFromMapping(containerId, c.GidMappings) if !found { return -1, fmt.Errorf("User namespaces enabled, but no group mapping found.") } return id, nil } // Return unchanged id. return containerId, nil } // HostRootGID gets the root gid for the process on host which could be non-zero // when user namespaces are enabled. func (c Config) HostRootGID() (int, error) { return c.HostGID(0) } // Utility function that gets a host ID for a container ID from user namespace map // if that ID is present in the map. func (c Config) hostIDFromMapping(containerID int, uMap []IDMap) (int, bool) { for _, m := range uMap { if (containerID >= m.ContainerID) && (containerID <= (m.ContainerID + m.Size - 1)) { hostID := m.HostID + (containerID - m.ContainerID) return hostID, true } } return -1, false } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/device.go000066400000000000000000000022511315410276000306330ustar00rootroot00000000000000package configs import ( "fmt" "os" ) const ( Wildcard = -1 ) // TODO Windows: This can be factored out in the future type Device struct { // Device type, block, char, etc. Type rune `json:"type"` // Path to the device. Path string `json:"path"` // Major is the device's major number. Major int64 `json:"major"` // Minor is the device's minor number. Minor int64 `json:"minor"` // Cgroup permissions format, rwm. Permissions string `json:"permissions"` // FileMode permission bits for the device. FileMode os.FileMode `json:"file_mode"` // Uid of the device. Uid uint32 `json:"uid"` // Gid of the device. Gid uint32 `json:"gid"` // Write the file to the allowed list Allow bool `json:"allow"` } func (d *Device) CgroupString() string { return fmt.Sprintf("%c %s:%s %s", d.Type, deviceNumberString(d.Major), deviceNumberString(d.Minor), d.Permissions) } func (d *Device) Mkdev() int { return int((d.Major << 8) | (d.Minor & 0xff) | ((d.Minor & 0xfff00) << 12)) } // deviceNumberString converts the device number to a string return result. func deviceNumberString(number int64) string { if number == Wildcard { return "*" } return fmt.Sprint(number) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go000066400000000000000000000037421315410276000325300ustar00rootroot00000000000000// +build linux freebsd package configs var ( // DefaultSimpleDevices are devices that are to be both allowed and created. DefaultSimpleDevices = []*Device{ // /dev/null and zero { Path: "/dev/null", Type: 'c', Major: 1, Minor: 3, Permissions: "rwm", FileMode: 0666, }, { Path: "/dev/zero", Type: 'c', Major: 1, Minor: 5, Permissions: "rwm", FileMode: 0666, }, { Path: "/dev/full", Type: 'c', Major: 1, Minor: 7, Permissions: "rwm", FileMode: 0666, }, // consoles and ttys { Path: "/dev/tty", Type: 'c', Major: 5, Minor: 0, Permissions: "rwm", FileMode: 0666, }, // /dev/urandom,/dev/random { Path: "/dev/urandom", Type: 'c', Major: 1, Minor: 9, Permissions: "rwm", FileMode: 0666, }, { Path: "/dev/random", Type: 'c', Major: 1, Minor: 8, Permissions: "rwm", FileMode: 0666, }, } DefaultAllowedDevices = append([]*Device{ // allow mknod for any device { Type: 'c', Major: Wildcard, Minor: Wildcard, Permissions: "m", }, { Type: 'b', Major: Wildcard, Minor: Wildcard, Permissions: "m", }, { Path: "/dev/console", Type: 'c', Major: 5, Minor: 1, Permissions: "rwm", }, // /dev/pts/ - pts namespaces are "coming soon" { Path: "", Type: 'c', Major: 136, Minor: Wildcard, Permissions: "rwm", }, { Path: "", Type: 'c', Major: 5, Minor: 2, Permissions: "rwm", }, // tuntap { Path: "", Type: 'c', Major: 10, Minor: 200, Permissions: "rwm", }, }, DefaultSimpleDevices...) DefaultAutoCreatedDevices = append([]*Device{}, DefaultSimpleDevices...) ) cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/hugepage_limit.go000066400000000000000000000002641315410276000323610ustar00rootroot00000000000000package configs type HugepageLimit struct { // which type of hugepage to limit. Pagesize string `json:"page_size"` // usage limit for hugepage. Limit uint64 `json:"limit"` } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go000066400000000000000000000003541315410276000341340ustar00rootroot00000000000000package configs import ( "fmt" ) type IfPrioMap struct { Interface string `json:"interface"` Priority int64 `json:"priority"` } func (i *IfPrioMap) CgroupString() string { return fmt.Sprintf("%s %d", i.Interface, i.Priority) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go000066400000000000000000000017531315410276000305440ustar00rootroot00000000000000package configs const ( // EXT_COPYUP is a directive to copy up the contents of a directory when // a tmpfs is mounted over it. EXT_COPYUP = 1 << iota ) type Mount struct { // Source path for the mount. Source string `json:"source"` // Destination path for the mount inside the container. Destination string `json:"destination"` // Device the mount is for. Device string `json:"device"` // Mount flags. Flags int `json:"flags"` // Propagation Flags PropagationFlags []int `json:"propagation_flags"` // Mount data applied to the mount. Data string `json:"data"` // Relabel source if set, "z" indicates shared, "Z" indicates unshared. Relabel string `json:"relabel"` // Extensions are additional flags that are specific to runc. Extensions int `json:"extensions"` // Optional Command to be run before Source is mounted. PremountCmds []Command `json:"premount_cmds"` // Optional Command to be run after Source is mounted. PostmountCmds []Command `json:"postmount_cmds"` } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces.go000066400000000000000000000001101315410276000315030ustar00rootroot00000000000000package configs type NamespaceType string type Namespaces []Namespace cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_linux.go000066400000000000000000000045331315410276000327370ustar00rootroot00000000000000package configs import ( "fmt" "os" "sync" ) const ( NEWNET NamespaceType = "NEWNET" NEWPID NamespaceType = "NEWPID" NEWNS NamespaceType = "NEWNS" NEWUTS NamespaceType = "NEWUTS" NEWIPC NamespaceType = "NEWIPC" NEWUSER NamespaceType = "NEWUSER" ) var ( nsLock sync.Mutex supportedNamespaces = make(map[NamespaceType]bool) ) // NsName converts the namespace type to its filename func NsName(ns NamespaceType) string { switch ns { case NEWNET: return "net" case NEWNS: return "mnt" case NEWPID: return "pid" case NEWIPC: return "ipc" case NEWUSER: return "user" case NEWUTS: return "uts" } return "" } // IsNamespaceSupported returns whether a namespace is available or // not func IsNamespaceSupported(ns NamespaceType) bool { nsLock.Lock() defer nsLock.Unlock() supported, ok := supportedNamespaces[ns] if ok { return supported } nsFile := NsName(ns) // if the namespace type is unknown, just return false if nsFile == "" { return false } _, err := os.Stat(fmt.Sprintf("/proc/self/ns/%s", nsFile)) // a namespace is supported if it exists and we have permissions to read it supported = err == nil supportedNamespaces[ns] = supported return supported } func NamespaceTypes() []NamespaceType { return []NamespaceType{ NEWUSER, // Keep user NS always first, don't move it. NEWIPC, NEWUTS, NEWNET, NEWPID, NEWNS, } } // Namespace defines configuration for each namespace. It specifies an // alternate path that is able to be joined via setns. type Namespace struct { Type NamespaceType `json:"type"` Path string `json:"path"` } func (n *Namespace) GetPath(pid int) string { return fmt.Sprintf("/proc/%d/ns/%s", pid, NsName(n.Type)) } func (n *Namespaces) Remove(t NamespaceType) bool { i := n.index(t) if i == -1 { return false } *n = append((*n)[:i], (*n)[i+1:]...) return true } func (n *Namespaces) Add(t NamespaceType, path string) { i := n.index(t) if i == -1 { *n = append(*n, Namespace{Type: t, Path: path}) return } (*n)[i].Path = path } func (n *Namespaces) index(t NamespaceType) int { for i, ns := range *n { if ns.Type == t { return i } } return -1 } func (n *Namespaces) Contains(t NamespaceType) bool { return n.index(t) != -1 } func (n *Namespaces) PathOf(t NamespaceType) string { i := n.index(t) if i == -1 { return "" } return (*n)[i].Path } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go000066400000000000000000000012601315410276000332440ustar00rootroot00000000000000// +build linux package configs import "golang.org/x/sys/unix" func (n *Namespace) Syscall() int { return namespaceInfo[n.Type] } var namespaceInfo = map[NamespaceType]int{ NEWNET: unix.CLONE_NEWNET, NEWNS: unix.CLONE_NEWNS, NEWUSER: unix.CLONE_NEWUSER, NEWIPC: unix.CLONE_NEWIPC, NEWUTS: unix.CLONE_NEWUTS, NEWPID: unix.CLONE_NEWPID, } // CloneFlags parses the container's Namespaces options to set the correct // flags on clone, unshare. This function returns flags only for new namespaces. func (n *Namespaces) CloneFlags() uintptr { var flag int for _, v := range *n { if v.Path != "" { continue } flag |= namespaceInfo[v.Type] } return uintptr(flag) } namespaces_syscall_unsupported.go000066400000000000000000000005531315410276000356410ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs// +build !linux,!windows package configs func (n *Namespace) Syscall() int { panic("No namespace syscall support") } // CloneFlags parses the container's Namespaces options to set the correct // flags on clone, unshare. This function returns flags only for new namespaces. func (n *Namespaces) CloneFlags() uintptr { panic("No namespace syscall support") } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go000066400000000000000000000002741315410276000341660ustar00rootroot00000000000000// +build !linux package configs // Namespace defines configuration for each namespace. It specifies an // alternate path that is able to be joined via setns. type Namespace struct { } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/network.go000066400000000000000000000055141315410276000310720ustar00rootroot00000000000000package configs // Network defines configuration for a container's networking stack // // The network configuration can be omitted from a container causing the // container to be setup with the host's networking stack type Network struct { // Type sets the networks type, commonly veth and loopback Type string `json:"type"` // Name of the network interface Name string `json:"name"` // The bridge to use. Bridge string `json:"bridge"` // MacAddress contains the MAC address to set on the network interface MacAddress string `json:"mac_address"` // Address contains the IPv4 and mask to set on the network interface Address string `json:"address"` // Gateway sets the gateway address that is used as the default for the interface Gateway string `json:"gateway"` // IPv6Address contains the IPv6 and mask to set on the network interface IPv6Address string `json:"ipv6_address"` // IPv6Gateway sets the ipv6 gateway address that is used as the default for the interface IPv6Gateway string `json:"ipv6_gateway"` // Mtu sets the mtu value for the interface and will be mirrored on both the host and // container's interfaces if a pair is created, specifically in the case of type veth // Note: This does not apply to loopback interfaces. Mtu int `json:"mtu"` // TxQueueLen sets the tx_queuelen value for the interface and will be mirrored on both the host and // container's interfaces if a pair is created, specifically in the case of type veth // Note: This does not apply to loopback interfaces. TxQueueLen int `json:"txqueuelen"` // HostInterfaceName is a unique name of a veth pair that resides on in the host interface of the // container. HostInterfaceName string `json:"host_interface_name"` // HairpinMode specifies if hairpin NAT should be enabled on the virtual interface // bridge port in the case of type veth // Note: This is unsupported on some systems. // Note: This does not apply to loopback interfaces. HairpinMode bool `json:"hairpin_mode"` } // Routes can be specified to create entries in the route table as the container is started // // All of destination, source, and gateway should be either IPv4 or IPv6. // One of the three options must be present, and omitted entries will use their // IP family default for the route table. For IPv4 for example, setting the // gateway to 1.2.3.4 and the interface to eth0 will set up a standard // destination of 0.0.0.0(or *) when viewed in the route table. type Route struct { // Sets the destination and mask, should be a CIDR. Accepts IPv4 and IPv6 Destination string `json:"destination"` // Sets the source and mask, should be a CIDR. Accepts IPv4 and IPv6 Source string `json:"source"` // Sets the gateway. Accepts IPv4 and IPv6 Gateway string `json:"gateway"` // The device to set this route up for, for example: eth0 InterfaceName string `json:"interface_name"` } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/validate/000077500000000000000000000000001315410276000306365ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/validate/rootless.go000066400000000000000000000072371315410276000330500ustar00rootroot00000000000000package validate import ( "fmt" "os" "reflect" "strings" "github.com/opencontainers/runc/libcontainer/configs" ) var ( geteuid = os.Geteuid getegid = os.Getegid ) func (v *ConfigValidator) rootless(config *configs.Config) error { if err := rootlessMappings(config); err != nil { return err } if err := rootlessMount(config); err != nil { return err } // Currently, cgroups cannot effectively be used in rootless containers. // The new cgroup namespace doesn't really help us either because it doesn't // have nice interactions with the user namespace (we're working with upstream // to fix this). if err := rootlessCgroup(config); err != nil { return err } // XXX: We currently can't verify the user config at all, because // configs.Config doesn't store the user-related configs. So this // has to be verified by setupUser() in init_linux.go. return nil } func rootlessMappings(config *configs.Config) error { rootuid, err := config.HostRootUID() if err != nil { return fmt.Errorf("failed to get root uid from uidMappings: %v", err) } if euid := geteuid(); euid != 0 { if !config.Namespaces.Contains(configs.NEWUSER) { return fmt.Errorf("rootless containers require user namespaces") } if rootuid != euid { return fmt.Errorf("rootless containers cannot map container root to a different host user") } } rootgid, err := config.HostRootGID() if err != nil { return fmt.Errorf("failed to get root gid from gidMappings: %v", err) } // Similar to the above test, we need to make sure that we aren't trying to // map to a group ID that we don't have the right to be. if rootgid != getegid() { return fmt.Errorf("rootless containers cannot map container root to a different host group") } // We can only map one user and group inside a container (our own). if len(config.UidMappings) != 1 || config.UidMappings[0].Size != 1 { return fmt.Errorf("rootless containers cannot map more than one user") } if len(config.GidMappings) != 1 || config.GidMappings[0].Size != 1 { return fmt.Errorf("rootless containers cannot map more than one group") } return nil } // cgroup verifies that the user isn't trying to set any cgroup limits or paths. func rootlessCgroup(config *configs.Config) error { // Nothing set at all. if config.Cgroups == nil || config.Cgroups.Resources == nil { return nil } // Used for comparing to the zero value. left := reflect.ValueOf(*config.Cgroups.Resources) right := reflect.Zero(left.Type()) // This is all we need to do, since specconv won't add cgroup options in // rootless mode. if !reflect.DeepEqual(left.Interface(), right.Interface()) { return fmt.Errorf("cannot specify resource limits in rootless container") } return nil } // mount verifies that the user isn't trying to set up any mounts they don't have // the rights to do. In addition, it makes sure that no mount has a `uid=` or // `gid=` option that doesn't resolve to root. func rootlessMount(config *configs.Config) error { // XXX: We could whitelist allowed devices at this point, but I'm not // convinced that's a good idea. The kernel is the best arbiter of // access control. for _, mount := range config.Mounts { // Check that the options list doesn't contain any uid= or gid= entries // that don't resolve to root. for _, opt := range strings.Split(mount.Data, ",") { if strings.HasPrefix(opt, "uid=") && opt != "uid=0" { return fmt.Errorf("cannot specify uid= mount options in rootless containers where argument isn't 0") } if strings.HasPrefix(opt, "gid=") && opt != "gid=0" { return fmt.Errorf("cannot specify gid= mount options in rootless containers where argument isn't 0") } } } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/configs/validate/validator.go000066400000000000000000000125301315410276000331530ustar00rootroot00000000000000package validate import ( "fmt" "os" "path/filepath" "strings" "github.com/opencontainers/runc/libcontainer/configs" selinux "github.com/opencontainers/selinux/go-selinux" ) type Validator interface { Validate(*configs.Config) error } func New() Validator { return &ConfigValidator{} } type ConfigValidator struct { } func (v *ConfigValidator) Validate(config *configs.Config) error { if err := v.rootfs(config); err != nil { return err } if err := v.network(config); err != nil { return err } if err := v.hostname(config); err != nil { return err } if err := v.security(config); err != nil { return err } if err := v.usernamespace(config); err != nil { return err } if err := v.sysctl(config); err != nil { return err } if config.Rootless { if err := v.rootless(config); err != nil { return err } } return nil } // rootfs validates if the rootfs is an absolute path and is not a symlink // to the container's root filesystem. func (v *ConfigValidator) rootfs(config *configs.Config) error { if _, err := os.Stat(config.Rootfs); err != nil { if os.IsNotExist(err) { return fmt.Errorf("rootfs (%s) does not exist", config.Rootfs) } return err } cleaned, err := filepath.Abs(config.Rootfs) if err != nil { return err } if cleaned, err = filepath.EvalSymlinks(cleaned); err != nil { return err } if filepath.Clean(config.Rootfs) != cleaned { return fmt.Errorf("%s is not an absolute path or is a symlink", config.Rootfs) } return nil } func (v *ConfigValidator) network(config *configs.Config) error { if !config.Namespaces.Contains(configs.NEWNET) { if len(config.Networks) > 0 || len(config.Routes) > 0 { return fmt.Errorf("unable to apply network settings without a private NET namespace") } } return nil } func (v *ConfigValidator) hostname(config *configs.Config) error { if config.Hostname != "" && !config.Namespaces.Contains(configs.NEWUTS) { return fmt.Errorf("unable to set hostname without a private UTS namespace") } return nil } func (v *ConfigValidator) security(config *configs.Config) error { // restrict sys without mount namespace if (len(config.MaskPaths) > 0 || len(config.ReadonlyPaths) > 0) && !config.Namespaces.Contains(configs.NEWNS) { return fmt.Errorf("unable to restrict sys entries without a private MNT namespace") } if config.ProcessLabel != "" && !selinux.GetEnabled() { return fmt.Errorf("selinux label is specified in config, but selinux is disabled or not supported") } return nil } func (v *ConfigValidator) usernamespace(config *configs.Config) error { if config.Namespaces.Contains(configs.NEWUSER) { if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { return fmt.Errorf("USER namespaces aren't enabled in the kernel") } } else { if config.UidMappings != nil || config.GidMappings != nil { return fmt.Errorf("User namespace mappings specified, but USER namespace isn't enabled in the config") } } return nil } // sysctl validates that the specified sysctl keys are valid or not. // /proc/sys isn't completely namespaced and depending on which namespaces // are specified, a subset of sysctls are permitted. func (v *ConfigValidator) sysctl(config *configs.Config) error { validSysctlMap := map[string]bool{ "kernel.msgmax": true, "kernel.msgmnb": true, "kernel.msgmni": true, "kernel.sem": true, "kernel.shmall": true, "kernel.shmmax": true, "kernel.shmmni": true, "kernel.shm_rmid_forced": true, } for s := range config.Sysctl { if validSysctlMap[s] || strings.HasPrefix(s, "fs.mqueue.") { if config.Namespaces.Contains(configs.NEWIPC) { continue } else { return fmt.Errorf("sysctl %q is not allowed in the hosts ipc namespace", s) } } if strings.HasPrefix(s, "net.") { if config.Namespaces.Contains(configs.NEWNET) { if path := config.Namespaces.PathOf(configs.NEWNET); path != "" { if err := checkHostNs(s, path); err != nil { return err } } continue } else { return fmt.Errorf("sysctl %q is not allowed in the hosts network namespace", s) } } return fmt.Errorf("sysctl %q is not in a separate kernel namespace", s) } return nil } func isSymbolicLink(path string) (bool, error) { fi, err := os.Lstat(path) if err != nil { return false, err } return fi.Mode()&os.ModeSymlink == os.ModeSymlink, nil } // checkHostNs checks whether network sysctl is used in host namespace. func checkHostNs(sysctlConfig string, path string) error { var currentProcessNetns = "/proc/self/ns/net" // readlink on the current processes network namespace destOfCurrentProcess, err := os.Readlink(currentProcessNetns) if err != nil { return fmt.Errorf("read soft link %q error", currentProcessNetns) } // First check if the provided path is a symbolic link symLink, err := isSymbolicLink(path) if err != nil { return fmt.Errorf("could not check that %q is a symlink: %v", path, err) } if symLink == false { // The provided namespace is not a symbolic link, // it is not the host namespace. return nil } // readlink on the path provided in the struct destOfContainer, err := os.Readlink(path) if err != nil { return fmt.Errorf("read soft link %q error", path) } if destOfContainer == destOfCurrentProcess { return fmt.Errorf("sysctl %q is not allowed in the hosts network namespace", sysctlConfig) } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/console.go000066400000000000000000000004271315410276000274110ustar00rootroot00000000000000package libcontainer import ( "io" "os" ) // Console represents a pseudo TTY. type Console interface { io.ReadWriteCloser // Path returns the filesystem path to the slave side of the pty. Path() string // Fd returns the fd for the master of the pty. File() *os.File } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/console_freebsd.go000066400000000000000000000005671315410276000311100ustar00rootroot00000000000000// +build freebsd package libcontainer import ( "errors" ) // newConsole returns an initialized console that can be used within a container by copying bytes // from the master side to the slave that is attached as the tty for the container's init process. func newConsole() (Console, error) { return nil, errors.New("libcontainer console is not supported on FreeBSD") } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/console_linux.go000066400000000000000000000075051315410276000306340ustar00rootroot00000000000000package libcontainer import ( "fmt" "os" "unsafe" "golang.org/x/sys/unix" ) func ConsoleFromFile(f *os.File) Console { return &linuxConsole{ master: f, } } // newConsole returns an initialized console that can be used within a container by copying bytes // from the master side to the slave that is attached as the tty for the container's init process. func newConsole() (Console, error) { master, err := os.OpenFile("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY|unix.O_CLOEXEC, 0) if err != nil { return nil, err } console, err := ptsname(master) if err != nil { return nil, err } if err := unlockpt(master); err != nil { return nil, err } return &linuxConsole{ slavePath: console, master: master, }, nil } // linuxConsole is a linux pseudo TTY for use within a container. type linuxConsole struct { master *os.File slavePath string } func (c *linuxConsole) File() *os.File { return c.master } func (c *linuxConsole) Path() string { return c.slavePath } func (c *linuxConsole) Read(b []byte) (int, error) { return c.master.Read(b) } func (c *linuxConsole) Write(b []byte) (int, error) { return c.master.Write(b) } func (c *linuxConsole) Close() error { if m := c.master; m != nil { return m.Close() } return nil } // mount initializes the console inside the rootfs mounting with the specified mount label // and applying the correct ownership of the console. func (c *linuxConsole) mount() error { oldMask := unix.Umask(0000) defer unix.Umask(oldMask) f, err := os.Create("/dev/console") if err != nil && !os.IsExist(err) { return err } if f != nil { f.Close() } return unix.Mount(c.slavePath, "/dev/console", "bind", unix.MS_BIND, "") } // dupStdio opens the slavePath for the console and dups the fds to the current // processes stdio, fd 0,1,2. func (c *linuxConsole) dupStdio() error { slave, err := c.open(unix.O_RDWR) if err != nil { return err } fd := int(slave.Fd()) for _, i := range []int{0, 1, 2} { if err := unix.Dup3(fd, i, 0); err != nil { return err } } return nil } // open is a clone of os.OpenFile without the O_CLOEXEC used to open the pty slave. func (c *linuxConsole) open(flag int) (*os.File, error) { r, e := unix.Open(c.slavePath, flag, 0) if e != nil { return nil, &os.PathError{ Op: "open", Path: c.slavePath, Err: e, } } return os.NewFile(uintptr(r), c.slavePath), nil } func ioctl(fd uintptr, flag, data uintptr) error { if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 { return err } return nil } // unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f. // unlockpt should be called before opening the slave side of a pty. func unlockpt(f *os.File) error { var u int32 return ioctl(f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) } // ptsname retrieves the name of the first available pts for the given master. func ptsname(f *os.File) (string, error) { n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN) if err != nil { return "", err } return fmt.Sprintf("/dev/pts/%d", n), nil } // SaneTerminal sets the necessary tty_ioctl(4)s to ensure that a pty pair // created by us acts normally. In particular, a not-very-well-known default of // Linux unix98 ptys is that they have +onlcr by default. While this isn't a // problem for terminal emulators, because we relay data from the terminal we // also relay that funky line discipline. func SaneTerminal(terminal *os.File) error { termios, err := unix.IoctlGetTermios(int(terminal.Fd()), unix.TCGETS) if err != nil { return fmt.Errorf("ioctl(tty, tcgets): %s", err.Error()) } // Set -onlcr so we don't have to deal with \r. termios.Oflag &^= unix.ONLCR if err := unix.IoctlSetTermios(int(terminal.Fd()), unix.TCSETS, termios); err != nil { return fmt.Errorf("ioctl(tty, tcsets): %s", err.Error()) } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/console_solaris.go000066400000000000000000000005441315410276000311450ustar00rootroot00000000000000package libcontainer import ( "errors" ) // newConsole returns an initialized console that can be used within a container by copying bytes // from the master side to the slave that is attached as the tty for the container's init process. func newConsole() (Console, error) { return nil, errors.New("libcontainer console is not supported on Solaris") } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/console_windows.go000066400000000000000000000011121315410276000311530ustar00rootroot00000000000000package libcontainer // newConsole returns an initialized console that can be used within a container func newConsole() (Console, error) { return &windowsConsole{}, nil } // windowsConsole is a Windows pseudo TTY for use within a container. type windowsConsole struct { } func (c *windowsConsole) Fd() uintptr { return 0 } func (c *windowsConsole) Path() string { return "" } func (c *windowsConsole) Read(b []byte) (int, error) { return 0, nil } func (c *windowsConsole) Write(b []byte) (int, error) { return 0, nil } func (c *windowsConsole) Close() error { return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/container.go000066400000000000000000000120461315410276000277310ustar00rootroot00000000000000// Package libcontainer provides a native Go implementation for creating containers // with namespaces, cgroups, capabilities, and filesystem access controls. // It allows you to manage the lifecycle of the container performing additional operations // after the container is created. package libcontainer import ( "os" "time" "github.com/opencontainers/runc/libcontainer/configs" ) // Status is the status of a container. type Status int const ( // Created is the status that denotes the container exists but has not been run yet. Created Status = iota // Running is the status that denotes the container exists and is running. Running // Pausing is the status that denotes the container exists, it is in the process of being paused. Pausing // Paused is the status that denotes the container exists, but all its processes are paused. Paused // Stopped is the status that denotes the container does not have a created or running process. Stopped ) func (s Status) String() string { switch s { case Created: return "created" case Running: return "running" case Pausing: return "pausing" case Paused: return "paused" case Stopped: return "stopped" default: return "unknown" } } // BaseState represents the platform agnostic pieces relating to a // running container's state type BaseState struct { // ID is the container ID. ID string `json:"id"` // InitProcessPid is the init process id in the parent namespace. InitProcessPid int `json:"init_process_pid"` // InitProcessStartTime is the init process start time in clock cycles since boot time. InitProcessStartTime uint64 `json:"init_process_start"` // Created is the unix timestamp for the creation time of the container in UTC Created time.Time `json:"created"` // Config is the container's configuration. Config configs.Config `json:"config"` } // BaseContainer is a libcontainer container object. // // Each container is thread-safe within the same process. Since a container can // be destroyed by a separate process, any function may return that the container // was not found. BaseContainer includes methods that are platform agnostic. type BaseContainer interface { // Returns the ID of the container ID() string // Returns the current status of the container. // // errors: // ContainerNotExists - Container no longer exists, // Systemerror - System error. Status() (Status, error) // State returns the current container's state information. // // errors: // SystemError - System error. State() (*State, error) // Returns the current config of the container. Config() configs.Config // Returns the PIDs inside this container. The PIDs are in the namespace of the calling process. // // errors: // ContainerNotExists - Container no longer exists, // Systemerror - System error. // // Some of the returned PIDs may no longer refer to processes in the Container, unless // the Container state is PAUSED in which case every PID in the slice is valid. Processes() ([]int, error) // Returns statistics for the container. // // errors: // ContainerNotExists - Container no longer exists, // Systemerror - System error. Stats() (*Stats, error) // Set resources of container as configured // // We can use this to change resources when containers are running. // // errors: // SystemError - System error. Set(config configs.Config) error // Start a process inside the container. Returns error if process fails to // start. You can track process lifecycle with passed Process structure. // // errors: // ContainerNotExists - Container no longer exists, // ConfigInvalid - config is invalid, // ContainerPaused - Container is paused, // SystemError - System error. Start(process *Process) (err error) // Run immediately starts the process inside the container. Returns error if process // fails to start. It does not block waiting for the exec fifo after start returns but // opens the fifo after start returns. // // errors: // ContainerNotExists - Container no longer exists, // ConfigInvalid - config is invalid, // ContainerPaused - Container is paused, // SystemError - System error. Run(process *Process) (err error) // Destroys the container, if its in a valid state, after killing any // remaining running processes. // // Any event registrations are removed before the container is destroyed. // No error is returned if the container is already destroyed. // // Running containers must first be stopped using Signal(..). // Paused containers must first be resumed using Resume(..). // // errors: // ContainerNotStopped - Container is still running, // ContainerPaused - Container is paused, // SystemError - System error. Destroy() error // Signal sends the provided signal code to the container's initial process. // // If all is specified the signal is sent to all processes in the container // including the initial process. // // errors: // SystemError - System error. Signal(s os.Signal, all bool) error // Exec signals the container to exec the users process at the end of the init. // // errors: // SystemError - System error. Exec() error } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/container_linux.go000066400000000000000000001325231315410276000311530ustar00rootroot00000000000000// +build linux package libcontainer import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "net" "os" "os/exec" "path/filepath" "reflect" "strings" "sync" "syscall" // only for SysProcAttr and Signal "time" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/criurpc" "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/utils" "github.com/golang/protobuf/proto" "github.com/sirupsen/logrus" "github.com/syndtr/gocapability/capability" "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) const stdioFdCount = 3 type linuxContainer struct { id string root string config *configs.Config cgroupManager cgroups.Manager initArgs []string initProcess parentProcess initProcessStartTime uint64 criuPath string m sync.Mutex criuVersion int state containerState created time.Time } // State represents a running container's state type State struct { BaseState // Platform specific fields below here // Specifies if the container was started under the rootless mode. Rootless bool `json:"rootless"` // Path to all the cgroups setup for a container. Key is cgroup subsystem name // with the value as the path. CgroupPaths map[string]string `json:"cgroup_paths"` // NamespacePaths are filepaths to the container's namespaces. Key is the namespace type // with the value as the path. NamespacePaths map[configs.NamespaceType]string `json:"namespace_paths"` // Container's standard descriptors (std{in,out,err}), needed for checkpoint and restore ExternalDescriptors []string `json:"external_descriptors,omitempty"` } // Container is a libcontainer container object. // // Each container is thread-safe within the same process. Since a container can // be destroyed by a separate process, any function may return that the container // was not found. type Container interface { BaseContainer // Methods below here are platform specific // Checkpoint checkpoints the running container's state to disk using the criu(8) utility. // // errors: // Systemerror - System error. Checkpoint(criuOpts *CriuOpts) error // Restore restores the checkpointed container to a running state using the criu(8) utility. // // errors: // Systemerror - System error. Restore(process *Process, criuOpts *CriuOpts) error // If the Container state is RUNNING or CREATED, sets the Container state to PAUSING and pauses // the execution of any user processes. Asynchronously, when the container finished being paused the // state is changed to PAUSED. // If the Container state is PAUSED, do nothing. // // errors: // ContainerNotExists - Container no longer exists, // ContainerNotRunning - Container not running or created, // Systemerror - System error. Pause() error // If the Container state is PAUSED, resumes the execution of any user processes in the // Container before setting the Container state to RUNNING. // If the Container state is RUNNING, do nothing. // // errors: // ContainerNotExists - Container no longer exists, // ContainerNotPaused - Container is not paused, // Systemerror - System error. Resume() error // NotifyOOM returns a read-only channel signaling when the container receives an OOM notification. // // errors: // Systemerror - System error. NotifyOOM() (<-chan struct{}, error) // NotifyMemoryPressure returns a read-only channel signaling when the container reaches a given pressure level // // errors: // Systemerror - System error. NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) } // ID returns the container's unique ID func (c *linuxContainer) ID() string { return c.id } // Config returns the container's configuration func (c *linuxContainer) Config() configs.Config { return *c.config } func (c *linuxContainer) Status() (Status, error) { c.m.Lock() defer c.m.Unlock() return c.currentStatus() } func (c *linuxContainer) State() (*State, error) { c.m.Lock() defer c.m.Unlock() return c.currentState() } func (c *linuxContainer) Processes() ([]int, error) { pids, err := c.cgroupManager.GetAllPids() if err != nil { return nil, newSystemErrorWithCause(err, "getting all container pids from cgroups") } return pids, nil } func (c *linuxContainer) Stats() (*Stats, error) { var ( err error stats = &Stats{} ) if stats.CgroupStats, err = c.cgroupManager.GetStats(); err != nil { return stats, newSystemErrorWithCause(err, "getting container stats from cgroups") } for _, iface := range c.config.Networks { switch iface.Type { case "veth": istats, err := getNetworkInterfaceStats(iface.HostInterfaceName) if err != nil { return stats, newSystemErrorWithCausef(err, "getting network stats for interface %q", iface.HostInterfaceName) } stats.Interfaces = append(stats.Interfaces, istats) } } return stats, nil } func (c *linuxContainer) Set(config configs.Config) error { c.m.Lock() defer c.m.Unlock() status, err := c.currentStatus() if err != nil { return err } if status == Stopped { return newGenericError(fmt.Errorf("container not running"), ContainerNotRunning) } if err := c.cgroupManager.Set(&config); err != nil { // Set configs back if err2 := c.cgroupManager.Set(c.config); err2 != nil { logrus.Warnf("Setting back cgroup configs failed due to error: %v, your state.json and actual configs might be inconsistent.", err2) } return err } // After config setting succeed, update config and states c.config = &config _, err = c.updateState(nil) return err } func (c *linuxContainer) Start(process *Process) error { c.m.Lock() defer c.m.Unlock() status, err := c.currentStatus() if err != nil { return err } if status == Stopped { if err := c.createExecFifo(); err != nil { return err } } if err := c.start(process, status == Stopped); err != nil { if status == Stopped { c.deleteExecFifo() } return err } return nil } func (c *linuxContainer) Run(process *Process) error { c.m.Lock() status, err := c.currentStatus() if err != nil { c.m.Unlock() return err } c.m.Unlock() if err := c.Start(process); err != nil { return err } if status == Stopped { return c.exec() } return nil } func (c *linuxContainer) Exec() error { c.m.Lock() defer c.m.Unlock() return c.exec() } func (c *linuxContainer) exec() error { path := filepath.Join(c.root, execFifoFilename) f, err := os.OpenFile(path, os.O_RDONLY, 0) if err != nil { return newSystemErrorWithCause(err, "open exec fifo for reading") } defer f.Close() data, err := ioutil.ReadAll(f) if err != nil { return err } if len(data) > 0 { os.Remove(path) return nil } return fmt.Errorf("cannot start an already running container") } func (c *linuxContainer) start(process *Process, isInit bool) error { parent, err := c.newParentProcess(process, isInit) if err != nil { return newSystemErrorWithCause(err, "creating new parent process") } if err := parent.start(); err != nil { // terminate the process to ensure that it properly is reaped. if err := parent.terminate(); err != nil { logrus.Warn(err) } return newSystemErrorWithCause(err, "starting container process") } // generate a timestamp indicating when the container was started c.created = time.Now().UTC() if isInit { c.state = &createdState{ c: c, } state, err := c.updateState(parent) if err != nil { return err } c.initProcessStartTime = state.InitProcessStartTime if c.config.Hooks != nil { s := configs.HookState{ Version: c.config.Version, ID: c.id, Pid: parent.pid(), Bundle: utils.SearchLabels(c.config.Labels, "bundle"), } for i, hook := range c.config.Hooks.Poststart { if err := hook.Run(s); err != nil { if err := parent.terminate(); err != nil { logrus.Warn(err) } return newSystemErrorWithCausef(err, "running poststart hook %d", i) } } } } else { c.state = &runningState{ c: c, } } return nil } func (c *linuxContainer) Signal(s os.Signal, all bool) error { if all { return signalAllProcesses(c.cgroupManager, s) } if err := c.initProcess.signal(s); err != nil { return newSystemErrorWithCause(err, "signaling init process") } return nil } func (c *linuxContainer) createExecFifo() error { rootuid, err := c.Config().HostRootUID() if err != nil { return err } rootgid, err := c.Config().HostRootGID() if err != nil { return err } fifoName := filepath.Join(c.root, execFifoFilename) if _, err := os.Stat(fifoName); err == nil { return fmt.Errorf("exec fifo %s already exists", fifoName) } oldMask := unix.Umask(0000) if err := unix.Mkfifo(fifoName, 0622); err != nil { unix.Umask(oldMask) return err } unix.Umask(oldMask) if err := os.Chown(fifoName, rootuid, rootgid); err != nil { return err } return nil } func (c *linuxContainer) deleteExecFifo() { fifoName := filepath.Join(c.root, execFifoFilename) os.Remove(fifoName) } // includeExecFifo opens the container's execfifo as a pathfd, so that the // container cannot access the statedir (and the FIFO itself remains // un-opened). It then adds the FifoFd to the given exec.Cmd as an inherited // fd, with _LIBCONTAINER_FIFOFD set to its fd number. func (c *linuxContainer) includeExecFifo(cmd *exec.Cmd) error { fifoName := filepath.Join(c.root, execFifoFilename) fifoFd, err := unix.Open(fifoName, unix.O_PATH|unix.O_CLOEXEC, 0) if err != nil { return err } cmd.ExtraFiles = append(cmd.ExtraFiles, os.NewFile(uintptr(fifoFd), fifoName)) cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_FIFOFD=%d", stdioFdCount+len(cmd.ExtraFiles)-1)) return nil } func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) { parentPipe, childPipe, err := utils.NewSockPair("init") if err != nil { return nil, newSystemErrorWithCause(err, "creating new init pipe") } cmd, err := c.commandTemplate(p, childPipe) if err != nil { return nil, newSystemErrorWithCause(err, "creating new command template") } if !doInit { return c.newSetnsProcess(p, cmd, parentPipe, childPipe) } // We only set up fifoFd if we're not doing a `runc exec`. The historic // reason for this is that previously we would pass a dirfd that allowed // for container rootfs escape (and not doing it in `runc exec` avoided // that problem), but we no longer do that. However, there's no need to do // this for `runc exec` so we just keep it this way to be safe. if err := c.includeExecFifo(cmd); err != nil { return nil, newSystemErrorWithCause(err, "including execfifo in cmd.Exec setup") } return c.newInitProcess(p, cmd, parentPipe, childPipe) } func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) { cmd := exec.Command(c.initArgs[0], c.initArgs[1:]...) cmd.Stdin = p.Stdin cmd.Stdout = p.Stdout cmd.Stderr = p.Stderr cmd.Dir = c.config.Rootfs if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } cmd.ExtraFiles = append(cmd.ExtraFiles, p.ExtraFiles...) if p.ConsoleSocket != nil { cmd.ExtraFiles = append(cmd.ExtraFiles, p.ConsoleSocket) cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_CONSOLE=%d", stdioFdCount+len(cmd.ExtraFiles)-1), ) } cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe) cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1), ) // NOTE: when running a container with no PID namespace and the parent process spawning the container is // PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason // even with the parent still running. if c.config.ParentDeathSignal > 0 { cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal) } return cmd, nil } func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) { cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard)) nsMaps := make(map[configs.NamespaceType]string) for _, ns := range c.config.Namespaces { if ns.Path != "" { nsMaps[ns.Type] = ns.Path } } _, sharePidns := nsMaps[configs.NEWPID] data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps) if err != nil { return nil, err } return &initProcess{ cmd: cmd, childPipe: childPipe, parentPipe: parentPipe, manager: c.cgroupManager, config: c.newInitConfig(p), container: c, process: p, bootstrapData: data, sharePidns: sharePidns, }, nil } func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*setnsProcess, error) { cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns)) state, err := c.currentState() if err != nil { return nil, newSystemErrorWithCause(err, "getting container's current state") } // for setns process, we don't have to set cloneflags as the process namespaces // will only be set via setns syscall data, err := c.bootstrapData(0, state.NamespacePaths) if err != nil { return nil, err } return &setnsProcess{ cmd: cmd, cgroupPaths: c.cgroupManager.GetPaths(), childPipe: childPipe, parentPipe: parentPipe, config: c.newInitConfig(p), process: p, bootstrapData: data, }, nil } func (c *linuxContainer) newInitConfig(process *Process) *initConfig { cfg := &initConfig{ Config: c.config, Args: process.Args, Env: process.Env, User: process.User, AdditionalGroups: process.AdditionalGroups, Cwd: process.Cwd, Capabilities: process.Capabilities, PassedFilesCount: len(process.ExtraFiles), ContainerId: c.ID(), NoNewPrivileges: c.config.NoNewPrivileges, Rootless: c.config.Rootless, AppArmorProfile: c.config.AppArmorProfile, ProcessLabel: c.config.ProcessLabel, Rlimits: c.config.Rlimits, } if process.NoNewPrivileges != nil { cfg.NoNewPrivileges = *process.NoNewPrivileges } if process.AppArmorProfile != "" { cfg.AppArmorProfile = process.AppArmorProfile } if process.Label != "" { cfg.ProcessLabel = process.Label } if len(process.Rlimits) > 0 { cfg.Rlimits = process.Rlimits } cfg.CreateConsole = process.ConsoleSocket != nil return cfg } func (c *linuxContainer) Destroy() error { c.m.Lock() defer c.m.Unlock() return c.state.destroy() } func (c *linuxContainer) Pause() error { c.m.Lock() defer c.m.Unlock() status, err := c.currentStatus() if err != nil { return err } switch status { case Running, Created: if err := c.cgroupManager.Freeze(configs.Frozen); err != nil { return err } return c.state.transition(&pausedState{ c: c, }) } return newGenericError(fmt.Errorf("container not running or created: %s", status), ContainerNotRunning) } func (c *linuxContainer) Resume() error { c.m.Lock() defer c.m.Unlock() status, err := c.currentStatus() if err != nil { return err } if status != Paused { return newGenericError(fmt.Errorf("container not paused"), ContainerNotPaused) } if err := c.cgroupManager.Freeze(configs.Thawed); err != nil { return err } return c.state.transition(&runningState{ c: c, }) } func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) { // XXX(cyphar): This requires cgroups. if c.config.Rootless { return nil, fmt.Errorf("cannot get OOM notifications from rootless container") } return notifyOnOOM(c.cgroupManager.GetPaths()) } func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) { // XXX(cyphar): This requires cgroups. if c.config.Rootless { return nil, fmt.Errorf("cannot get memory pressure notifications from rootless container") } return notifyMemoryPressure(c.cgroupManager.GetPaths(), level) } var criuFeatures *criurpc.CriuFeatures func (c *linuxContainer) checkCriuFeatures(criuOpts *CriuOpts, rpcOpts *criurpc.CriuOpts, criuFeat *criurpc.CriuFeatures) error { var t criurpc.CriuReqType t = criurpc.CriuReqType_FEATURE_CHECK // criu 1.8 => 10800 if err := c.checkCriuVersion(10800); err != nil { // Feature checking was introduced with CRIU 1.8. // Ignore the feature check if an older CRIU version is used // and just act as before. // As all automated PR testing is done using CRIU 1.7 this // code will not be tested by automated PR testing. return nil } // make sure the features we are looking for are really not from // some previous check criuFeatures = nil req := &criurpc.CriuReq{ Type: &t, // Theoretically this should not be necessary but CRIU // segfaults if Opts is empty. // Fixed in CRIU 2.12 Opts: rpcOpts, Features: criuFeat, } err := c.criuSwrk(nil, req, criuOpts, false) if err != nil { logrus.Debugf("%s", err) return fmt.Errorf("CRIU feature check failed") } logrus.Debugf("Feature check says: %s", criuFeatures) missingFeatures := false if *criuFeat.MemTrack && !*criuFeatures.MemTrack { missingFeatures = true logrus.Debugf("CRIU does not support MemTrack") } if missingFeatures { return fmt.Errorf("CRIU is missing features") } return nil } func parseCriuVersion(path string) (int, error) { var x, y, z int out, err := exec.Command(path, "-V").Output() if err != nil { return 0, fmt.Errorf("Unable to execute CRIU command: %s", path) } x = 0 y = 0 z = 0 if ep := strings.Index(string(out), "-"); ep >= 0 { // criu Git version format var version string if sp := strings.Index(string(out), "GitID"); sp > 0 { version = string(out)[sp:ep] } else { return 0, fmt.Errorf("Unable to parse the CRIU version: %s", path) } n, err := fmt.Sscanf(string(version), "GitID: v%d.%d.%d", &x, &y, &z) // 1.5.2 if err != nil { n, err = fmt.Sscanf(string(version), "GitID: v%d.%d", &x, &y) // 1.6 y++ } else { z++ } if n < 2 || err != nil { return 0, fmt.Errorf("Unable to parse the CRIU version: %s %d %s", version, n, err) } } else { // criu release version format n, err := fmt.Sscanf(string(out), "Version: %d.%d.%d\n", &x, &y, &z) // 1.5.2 if err != nil { n, err = fmt.Sscanf(string(out), "Version: %d.%d\n", &x, &y) // 1.6 } if n < 2 || err != nil { return 0, fmt.Errorf("Unable to parse the CRIU version: %s %d %s", out, n, err) } } return x*10000 + y*100 + z, nil } func compareCriuVersion(criuVersion int, minVersion int) error { // simple function to perform the actual version compare if criuVersion < minVersion { return fmt.Errorf("CRIU version %d must be %d or higher", criuVersion, minVersion) } return nil } // This is used to store the result of criu version RPC var criuVersionRPC *criurpc.CriuVersion // checkCriuVersion checks Criu version greater than or equal to minVersion func (c *linuxContainer) checkCriuVersion(minVersion int) error { // If the version of criu has already been determined there is no need // to ask criu for the version again. Use the value from c.criuVersion. if c.criuVersion != 0 { return compareCriuVersion(c.criuVersion, minVersion) } // First try if this version of CRIU support the version RPC. // The CRIU version RPC was introduced with CRIU 3.0. // First, reset the variable for the RPC answer to nil criuVersionRPC = nil var t criurpc.CriuReqType t = criurpc.CriuReqType_VERSION req := &criurpc.CriuReq{ Type: &t, } err := c.criuSwrk(nil, req, nil, false) if err != nil { return fmt.Errorf("CRIU version check failed: %s", err) } if criuVersionRPC != nil { logrus.Debugf("CRIU version: %s", criuVersionRPC) // major and minor are always set c.criuVersion = int(*criuVersionRPC.Major) * 10000 c.criuVersion += int(*criuVersionRPC.Minor) * 100 if criuVersionRPC.Sublevel != nil { c.criuVersion += int(*criuVersionRPC.Sublevel) } if criuVersionRPC.Gitid != nil { // runc's convention is that a CRIU git release is // always the same as increasing the minor by 1 c.criuVersion -= (c.criuVersion % 100) c.criuVersion += 100 } return compareCriuVersion(c.criuVersion, minVersion) } // This is CRIU without the version RPC and therefore // older than 3.0. Parsing the output is required. // This can be remove once runc does not work with criu older than 3.0 c.criuVersion, err = parseCriuVersion(c.criuPath) if err != nil { return err } return compareCriuVersion(c.criuVersion, minVersion) } const descriptorsFilename = "descriptors.json" func (c *linuxContainer) addCriuDumpMount(req *criurpc.CriuReq, m *configs.Mount) { mountDest := m.Destination if strings.HasPrefix(mountDest, c.config.Rootfs) { mountDest = mountDest[len(c.config.Rootfs):] } extMnt := &criurpc.ExtMountMap{ Key: proto.String(mountDest), Val: proto.String(mountDest), } req.Opts.ExtMnt = append(req.Opts.ExtMnt, extMnt) } func (c *linuxContainer) addMaskPaths(req *criurpc.CriuReq) error { for _, path := range c.config.MaskPaths { fi, err := os.Stat(fmt.Sprintf("/proc/%d/root/%s", c.initProcess.pid(), path)) if err != nil { if os.IsNotExist(err) { continue } return err } if fi.IsDir() { continue } extMnt := &criurpc.ExtMountMap{ Key: proto.String(path), Val: proto.String("/dev/null"), } req.Opts.ExtMnt = append(req.Opts.ExtMnt, extMnt) } return nil } func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error { c.m.Lock() defer c.m.Unlock() // TODO(avagin): Figure out how to make this work nicely. CRIU 2.0 has // support for doing unprivileged dumps, but the setup of // rootless containers might make this complicated. if c.config.Rootless { return fmt.Errorf("cannot checkpoint a rootless container") } // criu 1.5.2 => 10502 if err := c.checkCriuVersion(10502); err != nil { return err } if criuOpts.ImagesDirectory == "" { return fmt.Errorf("invalid directory to save checkpoint") } // Since a container can be C/R'ed multiple times, // the checkpoint directory may already exist. if err := os.Mkdir(criuOpts.ImagesDirectory, 0755); err != nil && !os.IsExist(err) { return err } if criuOpts.WorkDirectory == "" { criuOpts.WorkDirectory = filepath.Join(c.root, "criu.work") } if err := os.Mkdir(criuOpts.WorkDirectory, 0755); err != nil && !os.IsExist(err) { return err } workDir, err := os.Open(criuOpts.WorkDirectory) if err != nil { return err } defer workDir.Close() imageDir, err := os.Open(criuOpts.ImagesDirectory) if err != nil { return err } defer imageDir.Close() rpcOpts := criurpc.CriuOpts{ ImagesDirFd: proto.Int32(int32(imageDir.Fd())), WorkDirFd: proto.Int32(int32(workDir.Fd())), LogLevel: proto.Int32(4), LogFile: proto.String("dump.log"), Root: proto.String(c.config.Rootfs), ManageCgroups: proto.Bool(true), NotifyScripts: proto.Bool(true), Pid: proto.Int32(int32(c.initProcess.pid())), ShellJob: proto.Bool(criuOpts.ShellJob), LeaveRunning: proto.Bool(criuOpts.LeaveRunning), TcpEstablished: proto.Bool(criuOpts.TcpEstablished), ExtUnixSk: proto.Bool(criuOpts.ExternalUnixConnections), FileLocks: proto.Bool(criuOpts.FileLocks), EmptyNs: proto.Uint32(criuOpts.EmptyNs), OrphanPtsMaster: proto.Bool(true), AutoDedup: proto.Bool(criuOpts.AutoDedup), } fcg := c.cgroupManager.GetPaths()["freezer"] if fcg != "" { rpcOpts.FreezeCgroup = proto.String(fcg) } // append optional criu opts, e.g., page-server and port if criuOpts.PageServer.Address != "" && criuOpts.PageServer.Port != 0 { rpcOpts.Ps = &criurpc.CriuPageServerInfo{ Address: proto.String(criuOpts.PageServer.Address), Port: proto.Int32(criuOpts.PageServer.Port), } } //pre-dump may need parentImage param to complete iterative migration if criuOpts.ParentImage != "" { rpcOpts.ParentImg = proto.String(criuOpts.ParentImage) rpcOpts.TrackMem = proto.Bool(true) } // append optional manage cgroups mode if criuOpts.ManageCgroupsMode != 0 { // criu 1.7 => 10700 if err := c.checkCriuVersion(10700); err != nil { return err } mode := criurpc.CriuCgMode(criuOpts.ManageCgroupsMode) rpcOpts.ManageCgroupsMode = &mode } var t criurpc.CriuReqType if criuOpts.PreDump { feat := criurpc.CriuFeatures{ MemTrack: proto.Bool(true), } if err := c.checkCriuFeatures(criuOpts, &rpcOpts, &feat); err != nil { return err } t = criurpc.CriuReqType_PRE_DUMP } else { t = criurpc.CriuReqType_DUMP } req := &criurpc.CriuReq{ Type: &t, Opts: &rpcOpts, } //no need to dump these information in pre-dump if !criuOpts.PreDump { for _, m := range c.config.Mounts { switch m.Device { case "bind": c.addCriuDumpMount(req, m) case "cgroup": binds, err := getCgroupMounts(m) if err != nil { return err } for _, b := range binds { c.addCriuDumpMount(req, b) } } } if err := c.addMaskPaths(req); err != nil { return err } for _, node := range c.config.Devices { m := &configs.Mount{Destination: node.Path, Source: node.Path} c.addCriuDumpMount(req, m) } // Write the FD info to a file in the image directory fdsJSON, err := json.Marshal(c.initProcess.externalDescriptors()) if err != nil { return err } err = ioutil.WriteFile(filepath.Join(criuOpts.ImagesDirectory, descriptorsFilename), fdsJSON, 0655) if err != nil { return err } } err = c.criuSwrk(nil, req, criuOpts, false) if err != nil { return err } return nil } func (c *linuxContainer) addCriuRestoreMount(req *criurpc.CriuReq, m *configs.Mount) { mountDest := m.Destination if strings.HasPrefix(mountDest, c.config.Rootfs) { mountDest = mountDest[len(c.config.Rootfs):] } extMnt := &criurpc.ExtMountMap{ Key: proto.String(mountDest), Val: proto.String(m.Source), } req.Opts.ExtMnt = append(req.Opts.ExtMnt, extMnt) } func (c *linuxContainer) restoreNetwork(req *criurpc.CriuReq, criuOpts *CriuOpts) { for _, iface := range c.config.Networks { switch iface.Type { case "veth": veth := new(criurpc.CriuVethPair) veth.IfOut = proto.String(iface.HostInterfaceName) veth.IfIn = proto.String(iface.Name) req.Opts.Veths = append(req.Opts.Veths, veth) case "loopback": // Do nothing } } for _, i := range criuOpts.VethPairs { veth := new(criurpc.CriuVethPair) veth.IfOut = proto.String(i.HostInterfaceName) veth.IfIn = proto.String(i.ContainerInterfaceName) req.Opts.Veths = append(req.Opts.Veths, veth) } } func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error { c.m.Lock() defer c.m.Unlock() // TODO(avagin): Figure out how to make this work nicely. CRIU doesn't have // support for unprivileged restore at the moment. if c.config.Rootless { return fmt.Errorf("cannot restore a rootless container") } // criu 1.5.2 => 10502 if err := c.checkCriuVersion(10502); err != nil { return err } if criuOpts.WorkDirectory == "" { criuOpts.WorkDirectory = filepath.Join(c.root, "criu.work") } // Since a container can be C/R'ed multiple times, // the work directory may already exist. if err := os.Mkdir(criuOpts.WorkDirectory, 0655); err != nil && !os.IsExist(err) { return err } workDir, err := os.Open(criuOpts.WorkDirectory) if err != nil { return err } defer workDir.Close() if criuOpts.ImagesDirectory == "" { return fmt.Errorf("invalid directory to restore checkpoint") } imageDir, err := os.Open(criuOpts.ImagesDirectory) if err != nil { return err } defer imageDir.Close() // CRIU has a few requirements for a root directory: // * it must be a mount point // * its parent must not be overmounted // c.config.Rootfs is bind-mounted to a temporary directory // to satisfy these requirements. root := filepath.Join(c.root, "criu-root") if err := os.Mkdir(root, 0755); err != nil { return err } defer os.Remove(root) root, err = filepath.EvalSymlinks(root) if err != nil { return err } err = unix.Mount(c.config.Rootfs, root, "", unix.MS_BIND|unix.MS_REC, "") if err != nil { return err } defer unix.Unmount(root, unix.MNT_DETACH) t := criurpc.CriuReqType_RESTORE req := &criurpc.CriuReq{ Type: &t, Opts: &criurpc.CriuOpts{ ImagesDirFd: proto.Int32(int32(imageDir.Fd())), WorkDirFd: proto.Int32(int32(workDir.Fd())), EvasiveDevices: proto.Bool(true), LogLevel: proto.Int32(4), LogFile: proto.String("restore.log"), RstSibling: proto.Bool(true), Root: proto.String(root), ManageCgroups: proto.Bool(true), NotifyScripts: proto.Bool(true), ShellJob: proto.Bool(criuOpts.ShellJob), ExtUnixSk: proto.Bool(criuOpts.ExternalUnixConnections), TcpEstablished: proto.Bool(criuOpts.TcpEstablished), FileLocks: proto.Bool(criuOpts.FileLocks), EmptyNs: proto.Uint32(criuOpts.EmptyNs), OrphanPtsMaster: proto.Bool(true), AutoDedup: proto.Bool(criuOpts.AutoDedup), }, } for _, m := range c.config.Mounts { switch m.Device { case "bind": c.addCriuRestoreMount(req, m) case "cgroup": binds, err := getCgroupMounts(m) if err != nil { return err } for _, b := range binds { c.addCriuRestoreMount(req, b) } } } if len(c.config.MaskPaths) > 0 { m := &configs.Mount{Destination: "/dev/null", Source: "/dev/null"} c.addCriuRestoreMount(req, m) } for _, node := range c.config.Devices { m := &configs.Mount{Destination: node.Path, Source: node.Path} c.addCriuRestoreMount(req, m) } if criuOpts.EmptyNs&unix.CLONE_NEWNET == 0 { c.restoreNetwork(req, criuOpts) } // append optional manage cgroups mode if criuOpts.ManageCgroupsMode != 0 { // criu 1.7 => 10700 if err := c.checkCriuVersion(10700); err != nil { return err } mode := criurpc.CriuCgMode(criuOpts.ManageCgroupsMode) req.Opts.ManageCgroupsMode = &mode } var ( fds []string fdJSON []byte ) if fdJSON, err = ioutil.ReadFile(filepath.Join(criuOpts.ImagesDirectory, descriptorsFilename)); err != nil { return err } if err := json.Unmarshal(fdJSON, &fds); err != nil { return err } for i := range fds { if s := fds[i]; strings.Contains(s, "pipe:") { inheritFd := new(criurpc.InheritFd) inheritFd.Key = proto.String(s) inheritFd.Fd = proto.Int32(int32(i)) req.Opts.InheritFd = append(req.Opts.InheritFd, inheritFd) } } return c.criuSwrk(process, req, criuOpts, true) } func (c *linuxContainer) criuApplyCgroups(pid int, req *criurpc.CriuReq) error { // XXX: Do we need to deal with this case? AFAIK criu still requires root. if err := c.cgroupManager.Apply(pid); err != nil { return err } if err := c.cgroupManager.Set(c.config); err != nil { return newSystemError(err) } path := fmt.Sprintf("/proc/%d/cgroup", pid) cgroupsPaths, err := cgroups.ParseCgroupFile(path) if err != nil { return err } for c, p := range cgroupsPaths { cgroupRoot := &criurpc.CgroupRoot{ Ctrl: proto.String(c), Path: proto.String(p), } req.Opts.CgRoot = append(req.Opts.CgRoot, cgroupRoot) } return nil } func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *CriuOpts, applyCgroups bool) error { fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0) if err != nil { return err } var logPath string if opts != nil { logPath = filepath.Join(opts.WorkDirectory, req.GetOpts().GetLogFile()) } else { // For the VERSION RPC 'opts' is set to 'nil' and therefore // opts.WorkDirectory does not exist. Set logPath to "". logPath = "" } criuClient := os.NewFile(uintptr(fds[0]), "criu-transport-client") criuClientFileCon, err := net.FileConn(criuClient) criuClient.Close() if err != nil { return err } criuClientCon := criuClientFileCon.(*net.UnixConn) defer criuClientCon.Close() criuServer := os.NewFile(uintptr(fds[1]), "criu-transport-server") defer criuServer.Close() args := []string{"swrk", "3"} if c.criuVersion != 0 { // If the CRIU Version is still '0' then this is probably // the initial CRIU run to detect the version. Skip it. logrus.Debugf("Using CRIU %d at: %s", c.criuVersion, c.criuPath) } logrus.Debugf("Using CRIU with following args: %s", args) cmd := exec.Command(c.criuPath, args...) if process != nil { cmd.Stdin = process.Stdin cmd.Stdout = process.Stdout cmd.Stderr = process.Stderr } cmd.ExtraFiles = append(cmd.ExtraFiles, criuServer) if err := cmd.Start(); err != nil { return err } criuServer.Close() defer func() { criuClientCon.Close() _, err := cmd.Process.Wait() if err != nil { return } }() if applyCgroups { err := c.criuApplyCgroups(cmd.Process.Pid, req) if err != nil { return err } } var extFds []string if process != nil { extFds, err = getPipeFds(cmd.Process.Pid) if err != nil { return err } } logrus.Debugf("Using CRIU in %s mode", req.GetType().String()) // In the case of criurpc.CriuReqType_FEATURE_CHECK req.GetOpts() // should be empty. For older CRIU versions it still will be // available but empty. criurpc.CriuReqType_VERSION actually // has no req.GetOpts(). if !(req.GetType() == criurpc.CriuReqType_FEATURE_CHECK || req.GetType() == criurpc.CriuReqType_VERSION) { val := reflect.ValueOf(req.GetOpts()) v := reflect.Indirect(val) for i := 0; i < v.NumField(); i++ { st := v.Type() name := st.Field(i).Name if strings.HasPrefix(name, "XXX_") { continue } value := val.MethodByName("Get" + name).Call([]reflect.Value{}) logrus.Debugf("CRIU option %s with value %v", name, value[0]) } } data, err := proto.Marshal(req) if err != nil { return err } _, err = criuClientCon.Write(data) if err != nil { return err } buf := make([]byte, 10*4096) oob := make([]byte, 4096) for true { n, oobn, _, _, err := criuClientCon.ReadMsgUnix(buf, oob) if err != nil { return err } if n == 0 { return fmt.Errorf("unexpected EOF") } if n == len(buf) { return fmt.Errorf("buffer is too small") } resp := new(criurpc.CriuResp) err = proto.Unmarshal(buf[:n], resp) if err != nil { return err } if !resp.GetSuccess() { typeString := req.GetType().String() if typeString == "VERSION" { // If the VERSION RPC fails this probably means that the CRIU // version is too old for this RPC. Just return 'nil'. return nil } return fmt.Errorf("criu failed: type %s errno %d\nlog file: %s", typeString, resp.GetCrErrno(), logPath) } t := resp.GetType() switch { case t == criurpc.CriuReqType_VERSION: logrus.Debugf("CRIU version: %s", resp) criuVersionRPC = resp.GetVersion() break case t == criurpc.CriuReqType_FEATURE_CHECK: logrus.Debugf("Feature check says: %s", resp) criuFeatures = resp.GetFeatures() case t == criurpc.CriuReqType_NOTIFY: if err := c.criuNotifications(resp, process, opts, extFds, oob[:oobn]); err != nil { return err } t = criurpc.CriuReqType_NOTIFY req = &criurpc.CriuReq{ Type: &t, NotifySuccess: proto.Bool(true), } data, err = proto.Marshal(req) if err != nil { return err } _, err = criuClientCon.Write(data) if err != nil { return err } continue case t == criurpc.CriuReqType_RESTORE: case t == criurpc.CriuReqType_DUMP: case t == criurpc.CriuReqType_PRE_DUMP: default: return fmt.Errorf("unable to parse the response %s", resp.String()) } break } criuClientCon.CloseWrite() // cmd.Wait() waits cmd.goroutines which are used for proxying file descriptors. // Here we want to wait only the CRIU process. st, err := cmd.Process.Wait() if err != nil { return err } // In pre-dump mode CRIU is in a loop and waits for // the final DUMP command. // The current runc pre-dump approach, however, is // start criu in PRE_DUMP once for a single pre-dump // and not the whole series of pre-dump, pre-dump, ...m, dump // If we got the message CriuReqType_PRE_DUMP it means // CRIU was successful and we need to forcefully stop CRIU if !st.Success() && *req.Type != criurpc.CriuReqType_PRE_DUMP { return fmt.Errorf("criu failed: %s\nlog file: %s", st.String(), logPath) } return nil } // block any external network activity func lockNetwork(config *configs.Config) error { for _, config := range config.Networks { strategy, err := getStrategy(config.Type) if err != nil { return err } if err := strategy.detach(config); err != nil { return err } } return nil } func unlockNetwork(config *configs.Config) error { for _, config := range config.Networks { strategy, err := getStrategy(config.Type) if err != nil { return err } if err = strategy.attach(config); err != nil { return err } } return nil } func (c *linuxContainer) criuNotifications(resp *criurpc.CriuResp, process *Process, opts *CriuOpts, fds []string, oob []byte) error { notify := resp.GetNotify() if notify == nil { return fmt.Errorf("invalid response: %s", resp.String()) } logrus.Debugf("notify: %s\n", notify.GetScript()) switch { case notify.GetScript() == "post-dump": f, err := os.Create(filepath.Join(c.root, "checkpoint")) if err != nil { return err } f.Close() case notify.GetScript() == "network-unlock": if err := unlockNetwork(c.config); err != nil { return err } case notify.GetScript() == "network-lock": if err := lockNetwork(c.config); err != nil { return err } case notify.GetScript() == "setup-namespaces": if c.config.Hooks != nil { s := configs.HookState{ Version: c.config.Version, ID: c.id, Pid: int(notify.GetPid()), Bundle: utils.SearchLabels(c.config.Labels, "bundle"), } for i, hook := range c.config.Hooks.Prestart { if err := hook.Run(s); err != nil { return newSystemErrorWithCausef(err, "running prestart hook %d", i) } } } case notify.GetScript() == "post-restore": pid := notify.GetPid() r, err := newRestoredProcess(int(pid), fds) if err != nil { return err } process.ops = r if err := c.state.transition(&restoredState{ imageDir: opts.ImagesDirectory, c: c, }); err != nil { return err } // create a timestamp indicating when the restored checkpoint was started c.created = time.Now().UTC() if _, err := c.updateState(r); err != nil { return err } if err := os.Remove(filepath.Join(c.root, "checkpoint")); err != nil { if !os.IsNotExist(err) { logrus.Error(err) } } case notify.GetScript() == "orphan-pts-master": scm, err := unix.ParseSocketControlMessage(oob) if err != nil { return err } fds, err := unix.ParseUnixRights(&scm[0]) if err != nil { return err } master := os.NewFile(uintptr(fds[0]), "orphan-pts-master") defer master.Close() // While we can access console.master, using the API is a good idea. if err := utils.SendFd(process.ConsoleSocket, master); err != nil { return err } } return nil } func (c *linuxContainer) updateState(process parentProcess) (*State, error) { if process != nil { c.initProcess = process } state, err := c.currentState() if err != nil { return nil, err } err = c.saveState(state) if err != nil { return nil, err } return state, nil } func (c *linuxContainer) saveState(s *State) error { f, err := os.Create(filepath.Join(c.root, stateFilename)) if err != nil { return err } defer f.Close() return utils.WriteJSON(f, s) } func (c *linuxContainer) deleteState() error { return os.Remove(filepath.Join(c.root, stateFilename)) } func (c *linuxContainer) currentStatus() (Status, error) { if err := c.refreshState(); err != nil { return -1, err } return c.state.status(), nil } // refreshState needs to be called to verify that the current state on the // container is what is true. Because consumers of libcontainer can use it // out of process we need to verify the container's status based on runtime // information and not rely on our in process info. func (c *linuxContainer) refreshState() error { paused, err := c.isPaused() if err != nil { return err } if paused { return c.state.transition(&pausedState{c: c}) } t, err := c.runType() if err != nil { return err } switch t { case Created: return c.state.transition(&createdState{c: c}) case Running: return c.state.transition(&runningState{c: c}) } return c.state.transition(&stoppedState{c: c}) } func (c *linuxContainer) runType() (Status, error) { if c.initProcess == nil { return Stopped, nil } pid := c.initProcess.pid() stat, err := system.Stat(pid) if err != nil { return Stopped, nil } if stat.StartTime != c.initProcessStartTime || stat.State == system.Zombie || stat.State == system.Dead { return Stopped, nil } // We'll create exec fifo and blocking on it after container is created, // and delete it after start container. if _, err := os.Stat(filepath.Join(c.root, execFifoFilename)); err == nil { return Created, nil } return Running, nil } func (c *linuxContainer) isPaused() (bool, error) { fcg := c.cgroupManager.GetPaths()["freezer"] if fcg == "" { // A container doesn't have a freezer cgroup return false, nil } data, err := ioutil.ReadFile(filepath.Join(fcg, "freezer.state")) if err != nil { // If freezer cgroup is not mounted, the container would just be not paused. if os.IsNotExist(err) { return false, nil } return false, newSystemErrorWithCause(err, "checking if container is paused") } return bytes.Equal(bytes.TrimSpace(data), []byte("FROZEN")), nil } func (c *linuxContainer) currentState() (*State, error) { var ( startTime uint64 externalDescriptors []string pid = -1 ) if c.initProcess != nil { pid = c.initProcess.pid() startTime, _ = c.initProcess.startTime() externalDescriptors = c.initProcess.externalDescriptors() } state := &State{ BaseState: BaseState{ ID: c.ID(), Config: *c.config, InitProcessPid: pid, InitProcessStartTime: startTime, Created: c.created, }, Rootless: c.config.Rootless, CgroupPaths: c.cgroupManager.GetPaths(), NamespacePaths: make(map[configs.NamespaceType]string), ExternalDescriptors: externalDescriptors, } if pid > 0 { for _, ns := range c.config.Namespaces { state.NamespacePaths[ns.Type] = ns.GetPath(pid) } for _, nsType := range configs.NamespaceTypes() { if !configs.IsNamespaceSupported(nsType) { continue } if _, ok := state.NamespacePaths[nsType]; !ok { ns := configs.Namespace{Type: nsType} state.NamespacePaths[ns.Type] = ns.GetPath(pid) } } } return state, nil } // orderNamespacePaths sorts namespace paths into a list of paths that we // can setns in order. func (c *linuxContainer) orderNamespacePaths(namespaces map[configs.NamespaceType]string) ([]string, error) { paths := []string{} for _, ns := range configs.NamespaceTypes() { // Remove namespaces that we don't need to join. if !c.config.Namespaces.Contains(ns) { continue } if p, ok := namespaces[ns]; ok && p != "" { // check if the requested namespace is supported if !configs.IsNamespaceSupported(ns) { return nil, newSystemError(fmt.Errorf("namespace %s is not supported", ns)) } // only set to join this namespace if it exists if _, err := os.Lstat(p); err != nil { return nil, newSystemErrorWithCausef(err, "running lstat on namespace path %q", p) } // do not allow namespace path with comma as we use it to separate // the namespace paths if strings.ContainsRune(p, ',') { return nil, newSystemError(fmt.Errorf("invalid path %s", p)) } paths = append(paths, fmt.Sprintf("%s:%s", configs.NsName(ns), p)) } } return paths, nil } func encodeIDMapping(idMap []configs.IDMap) ([]byte, error) { data := bytes.NewBuffer(nil) for _, im := range idMap { line := fmt.Sprintf("%d %d %d\n", im.ContainerID, im.HostID, im.Size) if _, err := data.WriteString(line); err != nil { return nil, err } } return data.Bytes(), nil } // bootstrapData encodes the necessary data in netlink binary format // as a io.Reader. // Consumer can write the data to a bootstrap program // such as one that uses nsenter package to bootstrap the container's // init process correctly, i.e. with correct namespaces, uid/gid // mapping etc. func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string) (io.Reader, error) { // create the netlink message r := nl.NewNetlinkRequest(int(InitMsg), 0) // write cloneFlags r.AddData(&Int32msg{ Type: CloneFlagsAttr, Value: uint32(cloneFlags), }) // write custom namespace paths if len(nsMaps) > 0 { nsPaths, err := c.orderNamespacePaths(nsMaps) if err != nil { return nil, err } r.AddData(&Bytemsg{ Type: NsPathsAttr, Value: []byte(strings.Join(nsPaths, ",")), }) } // write namespace paths only when we are not joining an existing user ns _, joinExistingUser := nsMaps[configs.NEWUSER] if !joinExistingUser { // write uid mappings if len(c.config.UidMappings) > 0 { b, err := encodeIDMapping(c.config.UidMappings) if err != nil { return nil, err } r.AddData(&Bytemsg{ Type: UidmapAttr, Value: b, }) } // write gid mappings if len(c.config.GidMappings) > 0 { b, err := encodeIDMapping(c.config.GidMappings) if err != nil { return nil, err } r.AddData(&Bytemsg{ Type: GidmapAttr, Value: b, }) // The following only applies if we are root. if !c.config.Rootless { // check if we have CAP_SETGID to setgroup properly pid, err := capability.NewPid(os.Getpid()) if err != nil { return nil, err } if !pid.Get(capability.EFFECTIVE, capability.CAP_SETGID) { r.AddData(&Boolmsg{ Type: SetgroupAttr, Value: true, }) } } } } // write oom_score_adj r.AddData(&Bytemsg{ Type: OomScoreAdjAttr, Value: []byte(fmt.Sprintf("%d", c.config.OomScoreAdj)), }) // write rootless r.AddData(&Boolmsg{ Type: RootlessAttr, Value: c.config.Rootless, }) return bytes.NewReader(r.Serialize()), nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/container_solaris.go000066400000000000000000000007071315410276000314660ustar00rootroot00000000000000package libcontainer // State represents a running container's state type State struct { BaseState // Platform specific fields below here } // A libcontainer container object. // // Each container is thread-safe within the same process. Since a container can // be destroyed by a separate process, any function may return that the container // was not found. type Container interface { BaseContainer // Methods below here are platform specific } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/container_windows.go000066400000000000000000000007071315410276000315040ustar00rootroot00000000000000package libcontainer // State represents a running container's state type State struct { BaseState // Platform specific fields below here } // A libcontainer container object. // // Each container is thread-safe within the same process. Since a container can // be destroyed by a separate process, any function may return that the container // was not found. type Container interface { BaseContainer // Methods below here are platform specific } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/criu_opts_linux.go000066400000000000000000000036371315410276000312030ustar00rootroot00000000000000package libcontainer // cgroup restoring strategy provided by criu type cgMode uint32 const ( CRIU_CG_MODE_SOFT cgMode = 3 + iota // restore cgroup properties if only dir created by criu CRIU_CG_MODE_FULL // always restore all cgroups and their properties CRIU_CG_MODE_STRICT // restore all, requiring them to not present in the system CRIU_CG_MODE_DEFAULT // the same as CRIU_CG_MODE_SOFT ) type CriuPageServerInfo struct { Address string // IP address of CRIU page server Port int32 // port number of CRIU page server } type VethPairName struct { ContainerInterfaceName string HostInterfaceName string } type CriuOpts struct { ImagesDirectory string // directory for storing image files WorkDirectory string // directory to cd and write logs/pidfiles/stats to ParentImage string // direcotry for storing parent image files in pre-dump and dump LeaveRunning bool // leave container in running state after checkpoint TcpEstablished bool // checkpoint/restore established TCP connections ExternalUnixConnections bool // allow external unix connections ShellJob bool // allow to dump and restore shell jobs FileLocks bool // handle file locks, for safety PreDump bool // call criu predump to perform iterative checkpoint PageServer CriuPageServerInfo // allow to dump to criu page server VethPairs []VethPairName // pass the veth to criu when restore ManageCgroupsMode cgMode // dump or restore cgroup mode EmptyNs uint32 // don't c/r properties for namespace from this mask AutoDedup bool // auto deduplication for incremental dumps } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/criu_opts_windows.go000066400000000000000000000002431315410276000315240ustar00rootroot00000000000000package libcontainer // TODO Windows: This can ultimately be entirely factored out as criu is // a Unix concept not relevant on Windows. type CriuOpts struct { } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/criurpc/000077500000000000000000000000001315410276000270645ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/criurpc/Makefile000066400000000000000000000000641315410276000305240ustar00rootroot00000000000000gen: criurpc.proto protoc --go_out=. criurpc.proto cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/criurpc/criurpc.pb.go000066400000000000000000001224521315410276000314700ustar00rootroot00000000000000// Code generated by protoc-gen-go. // source: criurpc.proto // DO NOT EDIT! /* Package criurpc is a generated protocol buffer package. It is generated from these files: criurpc.proto It has these top-level messages: CriuPageServerInfo CriuVethPair ExtMountMap JoinNamespace InheritFd CgroupRoot UnixSk CriuOpts CriuDumpResp CriuRestoreResp CriuNotify CriuFeatures CriuReq CriuResp CriuVersion */ package criurpc import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type CriuCgMode int32 const ( CriuCgMode_IGNORE CriuCgMode = 0 CriuCgMode_CG_NONE CriuCgMode = 1 CriuCgMode_PROPS CriuCgMode = 2 CriuCgMode_SOFT CriuCgMode = 3 CriuCgMode_FULL CriuCgMode = 4 CriuCgMode_STRICT CriuCgMode = 5 CriuCgMode_DEFAULT CriuCgMode = 6 ) var CriuCgMode_name = map[int32]string{ 0: "IGNORE", 1: "CG_NONE", 2: "PROPS", 3: "SOFT", 4: "FULL", 5: "STRICT", 6: "DEFAULT", } var CriuCgMode_value = map[string]int32{ "IGNORE": 0, "CG_NONE": 1, "PROPS": 2, "SOFT": 3, "FULL": 4, "STRICT": 5, "DEFAULT": 6, } func (x CriuCgMode) Enum() *CriuCgMode { p := new(CriuCgMode) *p = x return p } func (x CriuCgMode) String() string { return proto.EnumName(CriuCgMode_name, int32(x)) } func (x *CriuCgMode) UnmarshalJSON(data []byte) error { value, err := proto.UnmarshalJSONEnum(CriuCgMode_value, data, "CriuCgMode") if err != nil { return err } *x = CriuCgMode(value) return nil } func (CriuCgMode) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } type CriuReqType int32 const ( CriuReqType_EMPTY CriuReqType = 0 CriuReqType_DUMP CriuReqType = 1 CriuReqType_RESTORE CriuReqType = 2 CriuReqType_CHECK CriuReqType = 3 CriuReqType_PRE_DUMP CriuReqType = 4 CriuReqType_PAGE_SERVER CriuReqType = 5 CriuReqType_NOTIFY CriuReqType = 6 CriuReqType_CPUINFO_DUMP CriuReqType = 7 CriuReqType_CPUINFO_CHECK CriuReqType = 8 CriuReqType_FEATURE_CHECK CriuReqType = 9 CriuReqType_VERSION CriuReqType = 10 ) var CriuReqType_name = map[int32]string{ 0: "EMPTY", 1: "DUMP", 2: "RESTORE", 3: "CHECK", 4: "PRE_DUMP", 5: "PAGE_SERVER", 6: "NOTIFY", 7: "CPUINFO_DUMP", 8: "CPUINFO_CHECK", 9: "FEATURE_CHECK", 10: "VERSION", } var CriuReqType_value = map[string]int32{ "EMPTY": 0, "DUMP": 1, "RESTORE": 2, "CHECK": 3, "PRE_DUMP": 4, "PAGE_SERVER": 5, "NOTIFY": 6, "CPUINFO_DUMP": 7, "CPUINFO_CHECK": 8, "FEATURE_CHECK": 9, "VERSION": 10, } func (x CriuReqType) Enum() *CriuReqType { p := new(CriuReqType) *p = x return p } func (x CriuReqType) String() string { return proto.EnumName(CriuReqType_name, int32(x)) } func (x *CriuReqType) UnmarshalJSON(data []byte) error { value, err := proto.UnmarshalJSONEnum(CriuReqType_value, data, "CriuReqType") if err != nil { return err } *x = CriuReqType(value) return nil } func (CriuReqType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } type CriuPageServerInfo struct { Address *string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Port *int32 `protobuf:"varint,2,opt,name=port" json:"port,omitempty"` Pid *int32 `protobuf:"varint,3,opt,name=pid" json:"pid,omitempty"` Fd *int32 `protobuf:"varint,4,opt,name=fd" json:"fd,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuPageServerInfo) Reset() { *m = CriuPageServerInfo{} } func (m *CriuPageServerInfo) String() string { return proto.CompactTextString(m) } func (*CriuPageServerInfo) ProtoMessage() {} func (*CriuPageServerInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } func (m *CriuPageServerInfo) GetAddress() string { if m != nil && m.Address != nil { return *m.Address } return "" } func (m *CriuPageServerInfo) GetPort() int32 { if m != nil && m.Port != nil { return *m.Port } return 0 } func (m *CriuPageServerInfo) GetPid() int32 { if m != nil && m.Pid != nil { return *m.Pid } return 0 } func (m *CriuPageServerInfo) GetFd() int32 { if m != nil && m.Fd != nil { return *m.Fd } return 0 } type CriuVethPair struct { IfIn *string `protobuf:"bytes,1,req,name=if_in,json=ifIn" json:"if_in,omitempty"` IfOut *string `protobuf:"bytes,2,req,name=if_out,json=ifOut" json:"if_out,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuVethPair) Reset() { *m = CriuVethPair{} } func (m *CriuVethPair) String() string { return proto.CompactTextString(m) } func (*CriuVethPair) ProtoMessage() {} func (*CriuVethPair) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } func (m *CriuVethPair) GetIfIn() string { if m != nil && m.IfIn != nil { return *m.IfIn } return "" } func (m *CriuVethPair) GetIfOut() string { if m != nil && m.IfOut != nil { return *m.IfOut } return "" } type ExtMountMap struct { Key *string `protobuf:"bytes,1,req,name=key" json:"key,omitempty"` Val *string `protobuf:"bytes,2,req,name=val" json:"val,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *ExtMountMap) Reset() { *m = ExtMountMap{} } func (m *ExtMountMap) String() string { return proto.CompactTextString(m) } func (*ExtMountMap) ProtoMessage() {} func (*ExtMountMap) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } func (m *ExtMountMap) GetKey() string { if m != nil && m.Key != nil { return *m.Key } return "" } func (m *ExtMountMap) GetVal() string { if m != nil && m.Val != nil { return *m.Val } return "" } type JoinNamespace struct { Ns *string `protobuf:"bytes,1,req,name=ns" json:"ns,omitempty"` NsFile *string `protobuf:"bytes,2,req,name=ns_file,json=nsFile" json:"ns_file,omitempty"` ExtraOpt *string `protobuf:"bytes,3,opt,name=extra_opt,json=extraOpt" json:"extra_opt,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *JoinNamespace) Reset() { *m = JoinNamespace{} } func (m *JoinNamespace) String() string { return proto.CompactTextString(m) } func (*JoinNamespace) ProtoMessage() {} func (*JoinNamespace) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } func (m *JoinNamespace) GetNs() string { if m != nil && m.Ns != nil { return *m.Ns } return "" } func (m *JoinNamespace) GetNsFile() string { if m != nil && m.NsFile != nil { return *m.NsFile } return "" } func (m *JoinNamespace) GetExtraOpt() string { if m != nil && m.ExtraOpt != nil { return *m.ExtraOpt } return "" } type InheritFd struct { Key *string `protobuf:"bytes,1,req,name=key" json:"key,omitempty"` Fd *int32 `protobuf:"varint,2,req,name=fd" json:"fd,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *InheritFd) Reset() { *m = InheritFd{} } func (m *InheritFd) String() string { return proto.CompactTextString(m) } func (*InheritFd) ProtoMessage() {} func (*InheritFd) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } func (m *InheritFd) GetKey() string { if m != nil && m.Key != nil { return *m.Key } return "" } func (m *InheritFd) GetFd() int32 { if m != nil && m.Fd != nil { return *m.Fd } return 0 } type CgroupRoot struct { Ctrl *string `protobuf:"bytes,1,opt,name=ctrl" json:"ctrl,omitempty"` Path *string `protobuf:"bytes,2,req,name=path" json:"path,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CgroupRoot) Reset() { *m = CgroupRoot{} } func (m *CgroupRoot) String() string { return proto.CompactTextString(m) } func (*CgroupRoot) ProtoMessage() {} func (*CgroupRoot) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } func (m *CgroupRoot) GetCtrl() string { if m != nil && m.Ctrl != nil { return *m.Ctrl } return "" } func (m *CgroupRoot) GetPath() string { if m != nil && m.Path != nil { return *m.Path } return "" } type UnixSk struct { Inode *uint32 `protobuf:"varint,1,req,name=inode" json:"inode,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *UnixSk) Reset() { *m = UnixSk{} } func (m *UnixSk) String() string { return proto.CompactTextString(m) } func (*UnixSk) ProtoMessage() {} func (*UnixSk) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } func (m *UnixSk) GetInode() uint32 { if m != nil && m.Inode != nil { return *m.Inode } return 0 } type CriuOpts struct { ImagesDirFd *int32 `protobuf:"varint,1,req,name=images_dir_fd,json=imagesDirFd" json:"images_dir_fd,omitempty"` Pid *int32 `protobuf:"varint,2,opt,name=pid" json:"pid,omitempty"` LeaveRunning *bool `protobuf:"varint,3,opt,name=leave_running,json=leaveRunning" json:"leave_running,omitempty"` ExtUnixSk *bool `protobuf:"varint,4,opt,name=ext_unix_sk,json=extUnixSk" json:"ext_unix_sk,omitempty"` TcpEstablished *bool `protobuf:"varint,5,opt,name=tcp_established,json=tcpEstablished" json:"tcp_established,omitempty"` EvasiveDevices *bool `protobuf:"varint,6,opt,name=evasive_devices,json=evasiveDevices" json:"evasive_devices,omitempty"` ShellJob *bool `protobuf:"varint,7,opt,name=shell_job,json=shellJob" json:"shell_job,omitempty"` FileLocks *bool `protobuf:"varint,8,opt,name=file_locks,json=fileLocks" json:"file_locks,omitempty"` LogLevel *int32 `protobuf:"varint,9,opt,name=log_level,json=logLevel,def=2" json:"log_level,omitempty"` LogFile *string `protobuf:"bytes,10,opt,name=log_file,json=logFile" json:"log_file,omitempty"` Ps *CriuPageServerInfo `protobuf:"bytes,11,opt,name=ps" json:"ps,omitempty"` NotifyScripts *bool `protobuf:"varint,12,opt,name=notify_scripts,json=notifyScripts" json:"notify_scripts,omitempty"` Root *string `protobuf:"bytes,13,opt,name=root" json:"root,omitempty"` ParentImg *string `protobuf:"bytes,14,opt,name=parent_img,json=parentImg" json:"parent_img,omitempty"` TrackMem *bool `protobuf:"varint,15,opt,name=track_mem,json=trackMem" json:"track_mem,omitempty"` AutoDedup *bool `protobuf:"varint,16,opt,name=auto_dedup,json=autoDedup" json:"auto_dedup,omitempty"` WorkDirFd *int32 `protobuf:"varint,17,opt,name=work_dir_fd,json=workDirFd" json:"work_dir_fd,omitempty"` LinkRemap *bool `protobuf:"varint,18,opt,name=link_remap,json=linkRemap" json:"link_remap,omitempty"` Veths []*CriuVethPair `protobuf:"bytes,19,rep,name=veths" json:"veths,omitempty"` CpuCap *uint32 `protobuf:"varint,20,opt,name=cpu_cap,json=cpuCap,def=4294967295" json:"cpu_cap,omitempty"` ForceIrmap *bool `protobuf:"varint,21,opt,name=force_irmap,json=forceIrmap" json:"force_irmap,omitempty"` ExecCmd []string `protobuf:"bytes,22,rep,name=exec_cmd,json=execCmd" json:"exec_cmd,omitempty"` ExtMnt []*ExtMountMap `protobuf:"bytes,23,rep,name=ext_mnt,json=extMnt" json:"ext_mnt,omitempty"` ManageCgroups *bool `protobuf:"varint,24,opt,name=manage_cgroups,json=manageCgroups" json:"manage_cgroups,omitempty"` CgRoot []*CgroupRoot `protobuf:"bytes,25,rep,name=cg_root,json=cgRoot" json:"cg_root,omitempty"` RstSibling *bool `protobuf:"varint,26,opt,name=rst_sibling,json=rstSibling" json:"rst_sibling,omitempty"` InheritFd []*InheritFd `protobuf:"bytes,27,rep,name=inherit_fd,json=inheritFd" json:"inherit_fd,omitempty"` AutoExtMnt *bool `protobuf:"varint,28,opt,name=auto_ext_mnt,json=autoExtMnt" json:"auto_ext_mnt,omitempty"` ExtSharing *bool `protobuf:"varint,29,opt,name=ext_sharing,json=extSharing" json:"ext_sharing,omitempty"` ExtMasters *bool `protobuf:"varint,30,opt,name=ext_masters,json=extMasters" json:"ext_masters,omitempty"` SkipMnt []string `protobuf:"bytes,31,rep,name=skip_mnt,json=skipMnt" json:"skip_mnt,omitempty"` EnableFs []string `protobuf:"bytes,32,rep,name=enable_fs,json=enableFs" json:"enable_fs,omitempty"` UnixSkIno []*UnixSk `protobuf:"bytes,33,rep,name=unix_sk_ino,json=unixSkIno" json:"unix_sk_ino,omitempty"` ManageCgroupsMode *CriuCgMode `protobuf:"varint,34,opt,name=manage_cgroups_mode,json=manageCgroupsMode,enum=CriuCgMode" json:"manage_cgroups_mode,omitempty"` GhostLimit *uint32 `protobuf:"varint,35,opt,name=ghost_limit,json=ghostLimit,def=1048576" json:"ghost_limit,omitempty"` IrmapScanPaths []string `protobuf:"bytes,36,rep,name=irmap_scan_paths,json=irmapScanPaths" json:"irmap_scan_paths,omitempty"` External []string `protobuf:"bytes,37,rep,name=external" json:"external,omitempty"` EmptyNs *uint32 `protobuf:"varint,38,opt,name=empty_ns,json=emptyNs" json:"empty_ns,omitempty"` JoinNs []*JoinNamespace `protobuf:"bytes,39,rep,name=join_ns,json=joinNs" json:"join_ns,omitempty"` CgroupProps *string `protobuf:"bytes,41,opt,name=cgroup_props,json=cgroupProps" json:"cgroup_props,omitempty"` CgroupPropsFile *string `protobuf:"bytes,42,opt,name=cgroup_props_file,json=cgroupPropsFile" json:"cgroup_props_file,omitempty"` CgroupDumpController []string `protobuf:"bytes,43,rep,name=cgroup_dump_controller,json=cgroupDumpController" json:"cgroup_dump_controller,omitempty"` FreezeCgroup *string `protobuf:"bytes,44,opt,name=freeze_cgroup,json=freezeCgroup" json:"freeze_cgroup,omitempty"` Timeout *uint32 `protobuf:"varint,45,opt,name=timeout" json:"timeout,omitempty"` TcpSkipInFlight *bool `protobuf:"varint,46,opt,name=tcp_skip_in_flight,json=tcpSkipInFlight" json:"tcp_skip_in_flight,omitempty"` WeakSysctls *bool `protobuf:"varint,47,opt,name=weak_sysctls,json=weakSysctls" json:"weak_sysctls,omitempty"` LazyPages *bool `protobuf:"varint,48,opt,name=lazy_pages,json=lazyPages" json:"lazy_pages,omitempty"` StatusFd *int32 `protobuf:"varint,49,opt,name=status_fd,json=statusFd" json:"status_fd,omitempty"` OrphanPtsMaster *bool `protobuf:"varint,50,opt,name=orphan_pts_master,json=orphanPtsMaster" json:"orphan_pts_master,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuOpts) Reset() { *m = CriuOpts{} } func (m *CriuOpts) String() string { return proto.CompactTextString(m) } func (*CriuOpts) ProtoMessage() {} func (*CriuOpts) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } const Default_CriuOpts_LogLevel int32 = 2 const Default_CriuOpts_CpuCap uint32 = 4294967295 const Default_CriuOpts_GhostLimit uint32 = 1048576 func (m *CriuOpts) GetImagesDirFd() int32 { if m != nil && m.ImagesDirFd != nil { return *m.ImagesDirFd } return 0 } func (m *CriuOpts) GetPid() int32 { if m != nil && m.Pid != nil { return *m.Pid } return 0 } func (m *CriuOpts) GetLeaveRunning() bool { if m != nil && m.LeaveRunning != nil { return *m.LeaveRunning } return false } func (m *CriuOpts) GetExtUnixSk() bool { if m != nil && m.ExtUnixSk != nil { return *m.ExtUnixSk } return false } func (m *CriuOpts) GetTcpEstablished() bool { if m != nil && m.TcpEstablished != nil { return *m.TcpEstablished } return false } func (m *CriuOpts) GetEvasiveDevices() bool { if m != nil && m.EvasiveDevices != nil { return *m.EvasiveDevices } return false } func (m *CriuOpts) GetShellJob() bool { if m != nil && m.ShellJob != nil { return *m.ShellJob } return false } func (m *CriuOpts) GetFileLocks() bool { if m != nil && m.FileLocks != nil { return *m.FileLocks } return false } func (m *CriuOpts) GetLogLevel() int32 { if m != nil && m.LogLevel != nil { return *m.LogLevel } return Default_CriuOpts_LogLevel } func (m *CriuOpts) GetLogFile() string { if m != nil && m.LogFile != nil { return *m.LogFile } return "" } func (m *CriuOpts) GetPs() *CriuPageServerInfo { if m != nil { return m.Ps } return nil } func (m *CriuOpts) GetNotifyScripts() bool { if m != nil && m.NotifyScripts != nil { return *m.NotifyScripts } return false } func (m *CriuOpts) GetRoot() string { if m != nil && m.Root != nil { return *m.Root } return "" } func (m *CriuOpts) GetParentImg() string { if m != nil && m.ParentImg != nil { return *m.ParentImg } return "" } func (m *CriuOpts) GetTrackMem() bool { if m != nil && m.TrackMem != nil { return *m.TrackMem } return false } func (m *CriuOpts) GetAutoDedup() bool { if m != nil && m.AutoDedup != nil { return *m.AutoDedup } return false } func (m *CriuOpts) GetWorkDirFd() int32 { if m != nil && m.WorkDirFd != nil { return *m.WorkDirFd } return 0 } func (m *CriuOpts) GetLinkRemap() bool { if m != nil && m.LinkRemap != nil { return *m.LinkRemap } return false } func (m *CriuOpts) GetVeths() []*CriuVethPair { if m != nil { return m.Veths } return nil } func (m *CriuOpts) GetCpuCap() uint32 { if m != nil && m.CpuCap != nil { return *m.CpuCap } return Default_CriuOpts_CpuCap } func (m *CriuOpts) GetForceIrmap() bool { if m != nil && m.ForceIrmap != nil { return *m.ForceIrmap } return false } func (m *CriuOpts) GetExecCmd() []string { if m != nil { return m.ExecCmd } return nil } func (m *CriuOpts) GetExtMnt() []*ExtMountMap { if m != nil { return m.ExtMnt } return nil } func (m *CriuOpts) GetManageCgroups() bool { if m != nil && m.ManageCgroups != nil { return *m.ManageCgroups } return false } func (m *CriuOpts) GetCgRoot() []*CgroupRoot { if m != nil { return m.CgRoot } return nil } func (m *CriuOpts) GetRstSibling() bool { if m != nil && m.RstSibling != nil { return *m.RstSibling } return false } func (m *CriuOpts) GetInheritFd() []*InheritFd { if m != nil { return m.InheritFd } return nil } func (m *CriuOpts) GetAutoExtMnt() bool { if m != nil && m.AutoExtMnt != nil { return *m.AutoExtMnt } return false } func (m *CriuOpts) GetExtSharing() bool { if m != nil && m.ExtSharing != nil { return *m.ExtSharing } return false } func (m *CriuOpts) GetExtMasters() bool { if m != nil && m.ExtMasters != nil { return *m.ExtMasters } return false } func (m *CriuOpts) GetSkipMnt() []string { if m != nil { return m.SkipMnt } return nil } func (m *CriuOpts) GetEnableFs() []string { if m != nil { return m.EnableFs } return nil } func (m *CriuOpts) GetUnixSkIno() []*UnixSk { if m != nil { return m.UnixSkIno } return nil } func (m *CriuOpts) GetManageCgroupsMode() CriuCgMode { if m != nil && m.ManageCgroupsMode != nil { return *m.ManageCgroupsMode } return CriuCgMode_IGNORE } func (m *CriuOpts) GetGhostLimit() uint32 { if m != nil && m.GhostLimit != nil { return *m.GhostLimit } return Default_CriuOpts_GhostLimit } func (m *CriuOpts) GetIrmapScanPaths() []string { if m != nil { return m.IrmapScanPaths } return nil } func (m *CriuOpts) GetExternal() []string { if m != nil { return m.External } return nil } func (m *CriuOpts) GetEmptyNs() uint32 { if m != nil && m.EmptyNs != nil { return *m.EmptyNs } return 0 } func (m *CriuOpts) GetJoinNs() []*JoinNamespace { if m != nil { return m.JoinNs } return nil } func (m *CriuOpts) GetCgroupProps() string { if m != nil && m.CgroupProps != nil { return *m.CgroupProps } return "" } func (m *CriuOpts) GetCgroupPropsFile() string { if m != nil && m.CgroupPropsFile != nil { return *m.CgroupPropsFile } return "" } func (m *CriuOpts) GetCgroupDumpController() []string { if m != nil { return m.CgroupDumpController } return nil } func (m *CriuOpts) GetFreezeCgroup() string { if m != nil && m.FreezeCgroup != nil { return *m.FreezeCgroup } return "" } func (m *CriuOpts) GetTimeout() uint32 { if m != nil && m.Timeout != nil { return *m.Timeout } return 0 } func (m *CriuOpts) GetTcpSkipInFlight() bool { if m != nil && m.TcpSkipInFlight != nil { return *m.TcpSkipInFlight } return false } func (m *CriuOpts) GetWeakSysctls() bool { if m != nil && m.WeakSysctls != nil { return *m.WeakSysctls } return false } func (m *CriuOpts) GetLazyPages() bool { if m != nil && m.LazyPages != nil { return *m.LazyPages } return false } func (m *CriuOpts) GetStatusFd() int32 { if m != nil && m.StatusFd != nil { return *m.StatusFd } return 0 } func (m *CriuOpts) GetOrphanPtsMaster() bool { if m != nil && m.OrphanPtsMaster != nil { return *m.OrphanPtsMaster } return false } type CriuDumpResp struct { Restored *bool `protobuf:"varint,1,opt,name=restored" json:"restored,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuDumpResp) Reset() { *m = CriuDumpResp{} } func (m *CriuDumpResp) String() string { return proto.CompactTextString(m) } func (*CriuDumpResp) ProtoMessage() {} func (*CriuDumpResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } func (m *CriuDumpResp) GetRestored() bool { if m != nil && m.Restored != nil { return *m.Restored } return false } type CriuRestoreResp struct { Pid *int32 `protobuf:"varint,1,req,name=pid" json:"pid,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuRestoreResp) Reset() { *m = CriuRestoreResp{} } func (m *CriuRestoreResp) String() string { return proto.CompactTextString(m) } func (*CriuRestoreResp) ProtoMessage() {} func (*CriuRestoreResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } func (m *CriuRestoreResp) GetPid() int32 { if m != nil && m.Pid != nil { return *m.Pid } return 0 } type CriuNotify struct { Script *string `protobuf:"bytes,1,opt,name=script" json:"script,omitempty"` Pid *int32 `protobuf:"varint,2,opt,name=pid" json:"pid,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuNotify) Reset() { *m = CriuNotify{} } func (m *CriuNotify) String() string { return proto.CompactTextString(m) } func (*CriuNotify) ProtoMessage() {} func (*CriuNotify) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } func (m *CriuNotify) GetScript() string { if m != nil && m.Script != nil { return *m.Script } return "" } func (m *CriuNotify) GetPid() int32 { if m != nil && m.Pid != nil { return *m.Pid } return 0 } // // List of features which can queried via // CRIU_REQ_TYPE__FEATURE_CHECK type CriuFeatures struct { MemTrack *bool `protobuf:"varint,1,opt,name=mem_track,json=memTrack" json:"mem_track,omitempty"` LazyPages *bool `protobuf:"varint,2,opt,name=lazy_pages,json=lazyPages" json:"lazy_pages,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuFeatures) Reset() { *m = CriuFeatures{} } func (m *CriuFeatures) String() string { return proto.CompactTextString(m) } func (*CriuFeatures) ProtoMessage() {} func (*CriuFeatures) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } func (m *CriuFeatures) GetMemTrack() bool { if m != nil && m.MemTrack != nil { return *m.MemTrack } return false } func (m *CriuFeatures) GetLazyPages() bool { if m != nil && m.LazyPages != nil { return *m.LazyPages } return false } type CriuReq struct { Type *CriuReqType `protobuf:"varint,1,req,name=type,enum=CriuReqType" json:"type,omitempty"` Opts *CriuOpts `protobuf:"bytes,2,opt,name=opts" json:"opts,omitempty"` NotifySuccess *bool `protobuf:"varint,3,opt,name=notify_success,json=notifySuccess" json:"notify_success,omitempty"` // // When set service won't close the connection but // will wait for more req-s to appear. Works not // for all request types. KeepOpen *bool `protobuf:"varint,4,opt,name=keep_open,json=keepOpen" json:"keep_open,omitempty"` // // 'features' can be used to query which features // are supported by the installed criu/kernel // via RPC. Features *CriuFeatures `protobuf:"bytes,5,opt,name=features" json:"features,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuReq) Reset() { *m = CriuReq{} } func (m *CriuReq) String() string { return proto.CompactTextString(m) } func (*CriuReq) ProtoMessage() {} func (*CriuReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } func (m *CriuReq) GetType() CriuReqType { if m != nil && m.Type != nil { return *m.Type } return CriuReqType_EMPTY } func (m *CriuReq) GetOpts() *CriuOpts { if m != nil { return m.Opts } return nil } func (m *CriuReq) GetNotifySuccess() bool { if m != nil && m.NotifySuccess != nil { return *m.NotifySuccess } return false } func (m *CriuReq) GetKeepOpen() bool { if m != nil && m.KeepOpen != nil { return *m.KeepOpen } return false } func (m *CriuReq) GetFeatures() *CriuFeatures { if m != nil { return m.Features } return nil } type CriuResp struct { Type *CriuReqType `protobuf:"varint,1,req,name=type,enum=CriuReqType" json:"type,omitempty"` Success *bool `protobuf:"varint,2,req,name=success" json:"success,omitempty"` Dump *CriuDumpResp `protobuf:"bytes,3,opt,name=dump" json:"dump,omitempty"` Restore *CriuRestoreResp `protobuf:"bytes,4,opt,name=restore" json:"restore,omitempty"` Notify *CriuNotify `protobuf:"bytes,5,opt,name=notify" json:"notify,omitempty"` Ps *CriuPageServerInfo `protobuf:"bytes,6,opt,name=ps" json:"ps,omitempty"` CrErrno *int32 `protobuf:"varint,7,opt,name=cr_errno,json=crErrno" json:"cr_errno,omitempty"` Features *CriuFeatures `protobuf:"bytes,8,opt,name=features" json:"features,omitempty"` CrErrmsg *string `protobuf:"bytes,9,opt,name=cr_errmsg,json=crErrmsg" json:"cr_errmsg,omitempty"` Version *CriuVersion `protobuf:"bytes,10,opt,name=version" json:"version,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuResp) Reset() { *m = CriuResp{} } func (m *CriuResp) String() string { return proto.CompactTextString(m) } func (*CriuResp) ProtoMessage() {} func (*CriuResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } func (m *CriuResp) GetType() CriuReqType { if m != nil && m.Type != nil { return *m.Type } return CriuReqType_EMPTY } func (m *CriuResp) GetSuccess() bool { if m != nil && m.Success != nil { return *m.Success } return false } func (m *CriuResp) GetDump() *CriuDumpResp { if m != nil { return m.Dump } return nil } func (m *CriuResp) GetRestore() *CriuRestoreResp { if m != nil { return m.Restore } return nil } func (m *CriuResp) GetNotify() *CriuNotify { if m != nil { return m.Notify } return nil } func (m *CriuResp) GetPs() *CriuPageServerInfo { if m != nil { return m.Ps } return nil } func (m *CriuResp) GetCrErrno() int32 { if m != nil && m.CrErrno != nil { return *m.CrErrno } return 0 } func (m *CriuResp) GetFeatures() *CriuFeatures { if m != nil { return m.Features } return nil } func (m *CriuResp) GetCrErrmsg() string { if m != nil && m.CrErrmsg != nil { return *m.CrErrmsg } return "" } func (m *CriuResp) GetVersion() *CriuVersion { if m != nil { return m.Version } return nil } // Answer for criu_req_type.VERSION requests type CriuVersion struct { Major *int32 `protobuf:"varint,1,req,name=major" json:"major,omitempty"` Minor *int32 `protobuf:"varint,2,req,name=minor" json:"minor,omitempty"` Gitid *string `protobuf:"bytes,3,opt,name=gitid" json:"gitid,omitempty"` Sublevel *int32 `protobuf:"varint,4,opt,name=sublevel" json:"sublevel,omitempty"` Extra *int32 `protobuf:"varint,5,opt,name=extra" json:"extra,omitempty"` Name *string `protobuf:"bytes,6,opt,name=name" json:"name,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *CriuVersion) Reset() { *m = CriuVersion{} } func (m *CriuVersion) String() string { return proto.CompactTextString(m) } func (*CriuVersion) ProtoMessage() {} func (*CriuVersion) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } func (m *CriuVersion) GetMajor() int32 { if m != nil && m.Major != nil { return *m.Major } return 0 } func (m *CriuVersion) GetMinor() int32 { if m != nil && m.Minor != nil { return *m.Minor } return 0 } func (m *CriuVersion) GetGitid() string { if m != nil && m.Gitid != nil { return *m.Gitid } return "" } func (m *CriuVersion) GetSublevel() int32 { if m != nil && m.Sublevel != nil { return *m.Sublevel } return 0 } func (m *CriuVersion) GetExtra() int32 { if m != nil && m.Extra != nil { return *m.Extra } return 0 } func (m *CriuVersion) GetName() string { if m != nil && m.Name != nil { return *m.Name } return "" } func init() { proto.RegisterType((*CriuPageServerInfo)(nil), "criu_page_server_info") proto.RegisterType((*CriuVethPair)(nil), "criu_veth_pair") proto.RegisterType((*ExtMountMap)(nil), "ext_mount_map") proto.RegisterType((*JoinNamespace)(nil), "join_namespace") proto.RegisterType((*InheritFd)(nil), "inherit_fd") proto.RegisterType((*CgroupRoot)(nil), "cgroup_root") proto.RegisterType((*UnixSk)(nil), "unix_sk") proto.RegisterType((*CriuOpts)(nil), "criu_opts") proto.RegisterType((*CriuDumpResp)(nil), "criu_dump_resp") proto.RegisterType((*CriuRestoreResp)(nil), "criu_restore_resp") proto.RegisterType((*CriuNotify)(nil), "criu_notify") proto.RegisterType((*CriuFeatures)(nil), "criu_features") proto.RegisterType((*CriuReq)(nil), "criu_req") proto.RegisterType((*CriuResp)(nil), "criu_resp") proto.RegisterType((*CriuVersion)(nil), "criu_version") proto.RegisterEnum("CriuCgMode", CriuCgMode_name, CriuCgMode_value) proto.RegisterEnum("CriuReqType", CriuReqType_name, CriuReqType_value) } func init() { proto.RegisterFile("criurpc.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 1781 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xdd, 0x72, 0x5b, 0xb7, 0x11, 0x0e, 0x29, 0xfe, 0x1c, 0x82, 0x3f, 0xa6, 0x10, 0xdb, 0x81, 0x93, 0xda, 0x62, 0xe8, 0x28, 0x51, 0x15, 0x97, 0x4d, 0x58, 0x3b, 0xae, 0x33, 0xed, 0x85, 0x47, 0x22, 0x5d, 0x36, 0x92, 0xc8, 0x01, 0x25, 0xcf, 0xe4, 0x0a, 0x73, 0x74, 0x0e, 0x48, 0xc1, 0x3c, 0x7f, 0x05, 0x40, 0x45, 0xf2, 0x83, 0xf4, 0x29, 0xfa, 0x0c, 0x7d, 0x84, 0xbe, 0x4e, 0x6f, 0x3b, 0xbb, 0x00, 0x65, 0x29, 0xc9, 0xb4, 0xbd, 0xc3, 0x7e, 0x58, 0x00, 0xbb, 0xfb, 0xed, 0x0f, 0x48, 0x3b, 0xd2, 0x6a, 0xad, 0x8b, 0x68, 0x50, 0xe8, 0xdc, 0xe6, 0xfd, 0x25, 0x79, 0x00, 0x80, 0x28, 0xc2, 0xa5, 0x14, 0x46, 0xea, 0x4b, 0xa9, 0x85, 0xca, 0x16, 0x39, 0x65, 0xa4, 0x1e, 0xc6, 0xb1, 0x96, 0xc6, 0xb0, 0x52, 0xaf, 0xb4, 0xd7, 0xe0, 0x1b, 0x91, 0x52, 0x52, 0x29, 0x72, 0x6d, 0x59, 0xb9, 0x57, 0xda, 0xab, 0x72, 0x5c, 0xd3, 0x2e, 0xd9, 0x2a, 0x54, 0xcc, 0xb6, 0x10, 0x82, 0x25, 0xed, 0x90, 0xf2, 0x22, 0x66, 0x15, 0x04, 0xca, 0x8b, 0xb8, 0xff, 0x27, 0xd2, 0xc1, 0x87, 0x2e, 0xa5, 0xbd, 0x10, 0x45, 0xa8, 0x34, 0xfd, 0x98, 0x54, 0xd5, 0x42, 0xa8, 0x8c, 0x95, 0x7a, 0xe5, 0xbd, 0x06, 0xaf, 0xa8, 0xc5, 0x24, 0xa3, 0x0f, 0x48, 0x4d, 0x2d, 0x44, 0xbe, 0x86, 0xeb, 0x01, 0xad, 0xaa, 0xc5, 0x74, 0x6d, 0xfb, 0x7f, 0x20, 0x6d, 0x79, 0x65, 0x45, 0x9a, 0xaf, 0x33, 0x2b, 0xd2, 0xb0, 0x80, 0x07, 0x57, 0xf2, 0xda, 0x1f, 0x85, 0x25, 0x20, 0x97, 0x61, 0xe2, 0x8f, 0xc1, 0xb2, 0xff, 0x96, 0x74, 0xde, 0xe5, 0x2a, 0x13, 0x59, 0x98, 0x4a, 0x53, 0x84, 0x91, 0x04, 0xa3, 0x32, 0xe3, 0x0f, 0x95, 0x33, 0x43, 0x3f, 0x21, 0xf5, 0xcc, 0x88, 0x85, 0x4a, 0xa4, 0x3f, 0x57, 0xcb, 0xcc, 0x58, 0x25, 0x92, 0x7e, 0x46, 0x1a, 0xf2, 0xca, 0xea, 0x50, 0xe4, 0x85, 0x45, 0xaf, 0x1a, 0x3c, 0x40, 0x60, 0x5a, 0xd8, 0xfe, 0x80, 0x10, 0x95, 0x5d, 0x48, 0xad, 0xac, 0x58, 0xc4, 0xbf, 0x62, 0x89, 0x73, 0x1d, 0x2e, 0x74, 0xae, 0xbf, 0x20, 0xcd, 0x68, 0xa9, 0xf3, 0x75, 0x21, 0x74, 0x9e, 0x5b, 0x88, 0x5f, 0x64, 0x75, 0xe2, 0xc3, 0x8a, 0x6b, 0x8c, 0x69, 0x68, 0x2f, 0xbc, 0x15, 0xb8, 0xee, 0xef, 0x90, 0xfa, 0x3a, 0x53, 0x57, 0xc2, 0xac, 0xe8, 0x7d, 0x52, 0x55, 0x59, 0x1e, 0x4b, 0x7c, 0xa5, 0xcd, 0x9d, 0xd0, 0xff, 0x57, 0x9b, 0x34, 0x30, 0xa6, 0x79, 0x61, 0x0d, 0xed, 0x93, 0xb6, 0x4a, 0xc3, 0xa5, 0x34, 0x22, 0x56, 0x5a, 0x2c, 0x62, 0xd4, 0xad, 0xf2, 0xa6, 0x03, 0x0f, 0x95, 0x1e, 0xc7, 0x1b, 0x9a, 0xca, 0x1f, 0x68, 0x7a, 0x4a, 0xda, 0x89, 0x0c, 0x2f, 0xa5, 0xd0, 0xeb, 0x2c, 0x53, 0xd9, 0x12, 0x9d, 0x0d, 0x78, 0x0b, 0x41, 0xee, 0x30, 0xfa, 0x84, 0x34, 0x21, 0xfa, 0xde, 0x1a, 0x24, 0x35, 0xe0, 0x10, 0xa0, 0xb3, 0x4c, 0x5d, 0xcd, 0x57, 0xf4, 0x2b, 0x72, 0xcf, 0x46, 0x85, 0x90, 0xc6, 0x86, 0xe7, 0x89, 0x32, 0x17, 0x32, 0x66, 0x55, 0xd4, 0xe9, 0xd8, 0xa8, 0x18, 0x7d, 0x40, 0x41, 0x51, 0x5e, 0x86, 0x46, 0x5d, 0x4a, 0x11, 0xcb, 0x4b, 0x15, 0x49, 0xc3, 0x6a, 0x4e, 0xd1, 0xc3, 0x87, 0x0e, 0x85, 0xf8, 0x9b, 0x0b, 0x99, 0x24, 0xe2, 0x5d, 0x7e, 0xce, 0xea, 0xa8, 0x12, 0x20, 0xf0, 0xd7, 0xfc, 0x9c, 0x3e, 0x26, 0x04, 0x28, 0x13, 0x49, 0x1e, 0xad, 0x0c, 0x0b, 0x9c, 0x35, 0x80, 0x1c, 0x01, 0x40, 0x9f, 0x90, 0x46, 0x92, 0x2f, 0x45, 0x22, 0x2f, 0x65, 0xc2, 0x1a, 0xe0, 0xea, 0xf7, 0xa5, 0x21, 0x0f, 0x92, 0x7c, 0x79, 0x04, 0x10, 0x7d, 0x44, 0x60, 0xed, 0x58, 0x27, 0x2e, 0xb5, 0x93, 0x7c, 0x89, 0xb4, 0x7f, 0x49, 0xca, 0x85, 0x61, 0xcd, 0x5e, 0x69, 0xaf, 0x39, 0x7c, 0x38, 0xf8, 0xd5, 0xc2, 0xe0, 0xe5, 0xc2, 0xd0, 0x5d, 0xd2, 0xc9, 0x72, 0xab, 0x16, 0xd7, 0xc2, 0x44, 0x5a, 0x15, 0xd6, 0xb0, 0x16, 0x5a, 0xd1, 0x76, 0xe8, 0xdc, 0x81, 0xc0, 0x2a, 0x30, 0xce, 0xda, 0x8e, 0x69, 0x64, 0xff, 0x31, 0x21, 0x45, 0xa8, 0x65, 0x66, 0x85, 0x4a, 0x97, 0xac, 0x83, 0x3b, 0x0d, 0x87, 0x4c, 0xd2, 0x25, 0x38, 0x6e, 0x75, 0x18, 0xad, 0x44, 0x2a, 0x53, 0x76, 0xcf, 0x39, 0x8e, 0xc0, 0xb1, 0x4c, 0xe1, 0x6c, 0xb8, 0xb6, 0xb9, 0x88, 0x65, 0xbc, 0x2e, 0x58, 0xd7, 0x39, 0x0e, 0xc8, 0x21, 0x00, 0x40, 0xd3, 0x4f, 0xb9, 0x5e, 0x6d, 0xf8, 0xdf, 0x46, 0x96, 0x1b, 0x00, 0x39, 0xf6, 0x1f, 0x13, 0x92, 0xa8, 0x6c, 0x25, 0xb4, 0x4c, 0xc3, 0x82, 0x51, 0x77, 0x1c, 0x10, 0x0e, 0x00, 0xdd, 0x25, 0x55, 0x28, 0x4e, 0xc3, 0x3e, 0xee, 0x6d, 0xed, 0x35, 0x87, 0xf7, 0x06, 0x77, 0xeb, 0x95, 0xbb, 0x5d, 0xfa, 0x94, 0xd4, 0xa3, 0x62, 0x2d, 0xa2, 0xb0, 0x60, 0xf7, 0x7b, 0xa5, 0xbd, 0xf6, 0xf7, 0xe4, 0xf9, 0xf0, 0xd5, 0xf3, 0x57, 0xdf, 0xbd, 0x1c, 0xbe, 0x7a, 0xc1, 0x6b, 0x51, 0xb1, 0x3e, 0x08, 0x0b, 0xba, 0x43, 0x9a, 0x8b, 0x5c, 0x47, 0x52, 0x28, 0x0d, 0x6f, 0x3d, 0xc0, 0xb7, 0x08, 0x42, 0x13, 0x40, 0x80, 0x04, 0x79, 0x25, 0x23, 0x11, 0xa5, 0x31, 0x7b, 0xd8, 0xdb, 0x02, 0x12, 0x40, 0x3e, 0x48, 0x21, 0x49, 0xea, 0x58, 0xeb, 0x99, 0x65, 0x9f, 0xa0, 0x25, 0x9d, 0xc1, 0x9d, 0xda, 0xe7, 0x35, 0x79, 0x65, 0x8f, 0x33, 0x0b, 0x2c, 0xa4, 0x61, 0x06, 0xfc, 0xb8, 0xf2, 0x32, 0x8c, 0x39, 0x16, 0x1c, 0x7a, 0xe0, 0x40, 0xba, 0x4b, 0xea, 0xd1, 0x12, 0x4b, 0x8f, 0x3d, 0xc2, 0xfb, 0x5a, 0x83, 0x5b, 0xe5, 0xc8, 0x6b, 0xd1, 0x92, 0x03, 0x31, 0x3b, 0xa4, 0xa9, 0x8d, 0x15, 0x46, 0x9d, 0x27, 0x50, 0x07, 0x9f, 0x3a, 0x93, 0xb5, 0xb1, 0x73, 0x87, 0xd0, 0xfd, 0xdb, 0x65, 0xcf, 0x3e, 0xc3, 0xab, 0x9a, 0x83, 0x0f, 0x10, 0x6f, 0xf8, 0xf5, 0x38, 0xa6, 0x3d, 0xd2, 0x42, 0xa6, 0x36, 0x8e, 0xfc, 0xc6, 0xdd, 0x06, 0xd8, 0xc8, 0x19, 0xbf, 0xe3, 0x6a, 0xca, 0x5c, 0x84, 0x1a, 0x9e, 0x7b, 0xec, 0x14, 0xe4, 0x95, 0x9d, 0x3b, 0x64, 0xa3, 0x90, 0x86, 0xc6, 0x4a, 0x6d, 0xd8, 0x93, 0x1b, 0x85, 0x63, 0x87, 0x40, 0x08, 0xcd, 0x4a, 0x15, 0x78, 0xff, 0x8e, 0x0b, 0x21, 0xc8, 0x70, 0x39, 0xb4, 0xaf, 0x2c, 0x3c, 0x4f, 0xa4, 0x58, 0x18, 0xd6, 0xc3, 0xbd, 0xc0, 0x01, 0x63, 0x43, 0xf7, 0x48, 0xd3, 0x57, 0xb2, 0x50, 0x59, 0xce, 0x3e, 0x47, 0x47, 0x82, 0x81, 0xc7, 0x78, 0x63, 0x8d, 0x45, 0x3d, 0xc9, 0x72, 0xfa, 0x67, 0xf2, 0xf1, 0xdd, 0x00, 0x8b, 0x14, 0x9a, 0x50, 0xbf, 0x57, 0xda, 0xeb, 0x0c, 0xdb, 0x2e, 0x3f, 0xa2, 0x25, 0x82, 0x7c, 0xfb, 0x4e, 0xd0, 0x8f, 0xf3, 0x58, 0xc2, 0x43, 0xcb, 0x8b, 0xdc, 0x58, 0x91, 0xa8, 0x54, 0x59, 0xf6, 0x14, 0xb3, 0xa5, 0xfe, 0xed, 0x37, 0xcf, 0xff, 0xf8, 0xe2, 0xe5, 0x77, 0x9c, 0xe0, 0xde, 0x11, 0x6c, 0xd1, 0x3d, 0xd2, 0xc5, 0x44, 0x11, 0x26, 0x0a, 0x33, 0x01, 0xdd, 0xcf, 0xb0, 0x2f, 0xd0, 0xec, 0x0e, 0xe2, 0xf3, 0x28, 0xcc, 0x66, 0x80, 0xd2, 0x4f, 0x21, 0x6f, 0xac, 0xd4, 0x59, 0x98, 0xb0, 0x5d, 0xef, 0x98, 0x97, 0x31, 0xa7, 0xd2, 0xc2, 0x5e, 0x8b, 0xcc, 0xb0, 0x2f, 0xe1, 0x31, 0x5e, 0x47, 0xf9, 0x04, 0x7c, 0xae, 0xbb, 0x51, 0x60, 0xd8, 0x57, 0x3e, 0xbb, 0xef, 0x8e, 0x06, 0x5e, 0x03, 0xf9, 0xc4, 0xd0, 0xcf, 0x49, 0xcb, 0x67, 0x47, 0xa1, 0xf3, 0xc2, 0xb0, 0xdf, 0x62, 0x85, 0xfa, 0x06, 0x3e, 0x03, 0x88, 0xee, 0x93, 0xed, 0xdb, 0x2a, 0xae, 0x93, 0xec, 0xa3, 0xde, 0xbd, 0x5b, 0x7a, 0xd8, 0x51, 0x9e, 0x93, 0x87, 0x5e, 0x37, 0x5e, 0xa7, 0x85, 0x88, 0xf2, 0xcc, 0xea, 0x3c, 0x49, 0xa4, 0x66, 0x5f, 0xa3, 0xf5, 0xf7, 0xdd, 0xee, 0xe1, 0x3a, 0x2d, 0x0e, 0x6e, 0xf6, 0xa0, 0x2b, 0x2f, 0xb4, 0x94, 0xef, 0x37, 0x81, 0x67, 0xcf, 0xf0, 0xf6, 0x96, 0x03, 0x5d, 0x8c, 0x61, 0x42, 0x5b, 0x95, 0x4a, 0x98, 0x95, 0xbf, 0x73, 0xde, 0x7a, 0x91, 0x7e, 0x4d, 0x28, 0xf4, 0x63, 0xcc, 0x0e, 0x95, 0x89, 0x45, 0xa2, 0x96, 0x17, 0x96, 0x0d, 0x30, 0x83, 0xa0, 0x53, 0xcf, 0x57, 0xaa, 0x98, 0x64, 0x63, 0x84, 0xc1, 0xe1, 0x9f, 0x64, 0xb8, 0x12, 0xe6, 0xda, 0x44, 0x36, 0x31, 0xec, 0xf7, 0xa8, 0xd6, 0x04, 0x6c, 0xee, 0x20, 0x6c, 0x1c, 0xe1, 0xfb, 0x6b, 0xec, 0x85, 0x86, 0x7d, 0xe3, 0x1b, 0x47, 0xf8, 0xfe, 0x7a, 0x06, 0x00, 0x36, 0x6b, 0x1b, 0xda, 0xb5, 0x81, 0xba, 0xf8, 0x16, 0xbb, 0x4e, 0xe0, 0x80, 0x71, 0x0c, 0xc1, 0xca, 0x75, 0x71, 0x01, 0xb4, 0x5a, 0xe3, 0xb3, 0x99, 0x0d, 0x9d, 0x29, 0x6e, 0x63, 0x66, 0x8d, 0x4b, 0xe9, 0xfe, 0x33, 0xff, 0x47, 0xc0, 0x50, 0x69, 0x69, 0x0a, 0xa0, 0x5b, 0x4b, 0x63, 0x73, 0x2d, 0x63, 0x9c, 0x97, 0x01, 0xbf, 0x91, 0xfb, 0xbb, 0x64, 0x1b, 0xb5, 0x3d, 0xe0, 0x0e, 0xf8, 0x09, 0xe7, 0x66, 0x1f, 0x2c, 0xfb, 0x2f, 0x49, 0x13, 0xd5, 0x5c, 0x6b, 0xa6, 0x0f, 0x49, 0xcd, 0xf5, 0x6c, 0x3f, 0x7f, 0xbd, 0xf4, 0xcb, 0xd1, 0xd8, 0xff, 0xc1, 0xfd, 0x95, 0xc4, 0x42, 0x86, 0x76, 0xad, 0x9d, 0x9f, 0xa9, 0x4c, 0x05, 0xb6, 0xe3, 0x8d, 0x35, 0xa9, 0x4c, 0x4f, 0x41, 0xfe, 0x59, 0x8c, 0xca, 0x3f, 0x8b, 0x51, 0xff, 0x9f, 0x25, 0x12, 0x78, 0x6b, 0xff, 0x46, 0xfb, 0xa4, 0x62, 0xaf, 0x0b, 0x37, 0xcd, 0x3b, 0xc3, 0xce, 0x60, 0xb3, 0x21, 0x00, 0xe5, 0xb8, 0x47, 0x9f, 0x90, 0x0a, 0x8c, 0x75, 0xbc, 0xa9, 0x39, 0x24, 0x83, 0x9b, 0x41, 0xcf, 0x11, 0xbf, 0x3d, 0x82, 0xd6, 0x51, 0x04, 0xdf, 0xb4, 0xad, 0x3b, 0x23, 0xc8, 0x81, 0x60, 0xf3, 0x4a, 0xca, 0x42, 0xe4, 0x85, 0xcc, 0xfc, 0xe0, 0x0e, 0x00, 0x98, 0x16, 0x32, 0xa3, 0xfb, 0x24, 0xd8, 0x38, 0x87, 0x03, 0xbb, 0xb9, 0xb1, 0x65, 0x83, 0xf2, 0x9b, 0xfd, 0xfe, 0xbf, 0xcb, 0xfe, 0xb3, 0x81, 0x61, 0xfe, 0x7f, 0x3c, 0x60, 0xa4, 0xbe, 0x31, 0x0d, 0xbe, 0x35, 0x01, 0xdf, 0x88, 0xf4, 0x29, 0xa9, 0x00, 0xc5, 0x68, 0xf1, 0xcd, 0xa0, 0xb9, 0x21, 0x9d, 0xe3, 0x26, 0x7d, 0x46, 0xea, 0x9e, 0x59, 0xb4, 0xbb, 0x39, 0xa4, 0x83, 0x5f, 0xd0, 0xcd, 0x37, 0x2a, 0xf4, 0x0b, 0x52, 0x73, 0x8e, 0x7b, 0x47, 0x5a, 0x83, 0x5b, 0xa4, 0x73, 0xbf, 0xe7, 0xe7, 0x7b, 0xed, 0x7f, 0xce, 0xf7, 0x47, 0x40, 0x96, 0x90, 0x5a, 0x67, 0x39, 0xfe, 0x3e, 0xaa, 0xbc, 0x1e, 0xe9, 0x11, 0x88, 0x77, 0x62, 0x16, 0xfc, 0xf7, 0x98, 0x41, 0xf0, 0xdd, 0x35, 0xa9, 0x59, 0xe2, 0x4f, 0xa4, 0xc1, 0x03, 0xbc, 0x27, 0x35, 0x4b, 0x18, 0x73, 0x97, 0x52, 0x1b, 0x95, 0x67, 0xf8, 0x0b, 0x69, 0x6e, 0x1a, 0xaa, 0x07, 0xf9, 0x66, 0xb7, 0xff, 0xf7, 0x12, 0x69, 0xdd, 0xde, 0x81, 0xdf, 0x60, 0x1a, 0xbe, 0xcb, 0xb5, 0xcf, 0x72, 0x27, 0x20, 0xaa, 0xb2, 0x5c, 0xfb, 0x8f, 0xa7, 0x13, 0x00, 0x5d, 0x2a, 0xeb, 0xbf, 0xe6, 0x0d, 0xee, 0x04, 0x28, 0x2b, 0xb3, 0x3e, 0x77, 0x3f, 0xa4, 0x8a, 0x2f, 0x58, 0x2f, 0xc3, 0x09, 0xfc, 0xe9, 0x62, 0x20, 0xab, 0xdc, 0x09, 0xf0, 0x95, 0x81, 0x5e, 0x89, 0xb1, 0x6b, 0x70, 0x5c, 0xef, 0x0b, 0x6f, 0x97, 0x1f, 0x01, 0x94, 0x90, 0xda, 0xe4, 0xcd, 0xc9, 0x94, 0x8f, 0xba, 0x1f, 0xd1, 0x26, 0xa9, 0x1f, 0xbc, 0x11, 0x27, 0xd3, 0x93, 0x51, 0xb7, 0x44, 0x1b, 0xa4, 0x3a, 0xe3, 0xd3, 0xd9, 0xbc, 0x5b, 0xa6, 0x01, 0xa9, 0xcc, 0xa7, 0xe3, 0xd3, 0xee, 0x16, 0xac, 0xc6, 0x67, 0x47, 0x47, 0xdd, 0x0a, 0x9c, 0x9b, 0x9f, 0xf2, 0xc9, 0xc1, 0x69, 0xb7, 0x0a, 0xe7, 0x0e, 0x47, 0xe3, 0xd7, 0x67, 0x47, 0xa7, 0xdd, 0xda, 0xfe, 0x3f, 0x4a, 0xbe, 0x04, 0x37, 0x99, 0x05, 0x37, 0x8d, 0x8e, 0x67, 0xa7, 0x3f, 0x76, 0x3f, 0x82, 0xf3, 0x87, 0x67, 0xc7, 0xb3, 0x6e, 0x09, 0xce, 0xf0, 0xd1, 0xfc, 0x14, 0x1e, 0x2e, 0x83, 0xc6, 0xc1, 0x5f, 0x46, 0x07, 0x3f, 0x74, 0xb7, 0x68, 0x8b, 0x04, 0x33, 0x3e, 0x12, 0xa8, 0x55, 0xa1, 0xf7, 0x48, 0x73, 0xf6, 0xfa, 0xcd, 0x48, 0xcc, 0x47, 0xfc, 0xed, 0x88, 0x77, 0xab, 0xf0, 0xec, 0xc9, 0xf4, 0x74, 0x32, 0xfe, 0xb1, 0x5b, 0xa3, 0x5d, 0xd2, 0x3a, 0x98, 0x9d, 0x4d, 0x4e, 0xc6, 0x53, 0xa7, 0x5e, 0xa7, 0xdb, 0xa4, 0xbd, 0x41, 0xdc, 0x7d, 0x01, 0x40, 0xe3, 0xd1, 0xeb, 0xd3, 0x33, 0x3e, 0xf2, 0x50, 0x03, 0x9e, 0x7e, 0x3b, 0xe2, 0xf3, 0xc9, 0xf4, 0xa4, 0x4b, 0xfe, 0x13, 0x00, 0x00, 0xff, 0xff, 0x5f, 0x2a, 0xaf, 0x49, 0x5b, 0x0d, 0x00, 0x00, } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/criurpc/criurpc.proto000066400000000000000000000114101315410276000316150ustar00rootroot00000000000000syntax = "proto2"; message criu_page_server_info { optional string address = 1; optional int32 port = 2; optional int32 pid = 3; optional int32 fd = 4; } message criu_veth_pair { required string if_in = 1; required string if_out = 2; }; message ext_mount_map { required string key = 1; required string val = 2; }; message join_namespace { required string ns = 1; required string ns_file = 2; optional string extra_opt = 3; } message inherit_fd { required string key = 1; required int32 fd = 2; }; message cgroup_root { optional string ctrl = 1; required string path = 2; }; message unix_sk { required uint32 inode = 1; }; enum criu_cg_mode { IGNORE = 0; CG_NONE = 1; PROPS = 2; SOFT = 3; FULL = 4; STRICT = 5; DEFAULT = 6; }; message criu_opts { required int32 images_dir_fd = 1; optional int32 pid = 2; /* if not set on dump, will dump requesting process */ optional bool leave_running = 3; optional bool ext_unix_sk = 4; optional bool tcp_established = 5; optional bool evasive_devices = 6; optional bool shell_job = 7; optional bool file_locks = 8; optional int32 log_level = 9 [default = 2]; optional string log_file = 10; /* No subdirs are allowed. Consider using work-dir */ optional criu_page_server_info ps = 11; optional bool notify_scripts = 12; optional string root = 13; optional string parent_img = 14; optional bool track_mem = 15; optional bool auto_dedup = 16; optional int32 work_dir_fd = 17; optional bool link_remap = 18; repeated criu_veth_pair veths = 19; /* DEPRECATED, use external instead */ optional uint32 cpu_cap = 20 [default = 0xffffffff]; optional bool force_irmap = 21; repeated string exec_cmd = 22; repeated ext_mount_map ext_mnt = 23; /* DEPRECATED, use external instead */ optional bool manage_cgroups = 24; /* backward compatibility */ repeated cgroup_root cg_root = 25; optional bool rst_sibling = 26; /* swrk only */ repeated inherit_fd inherit_fd = 27; /* swrk only */ optional bool auto_ext_mnt = 28; optional bool ext_sharing = 29; optional bool ext_masters = 30; repeated string skip_mnt = 31; repeated string enable_fs = 32; repeated unix_sk unix_sk_ino = 33; /* DEPRECATED, use external instead */ optional criu_cg_mode manage_cgroups_mode = 34; optional uint32 ghost_limit = 35 [default = 0x100000]; repeated string irmap_scan_paths = 36; repeated string external = 37; optional uint32 empty_ns = 38; repeated join_namespace join_ns = 39; optional string cgroup_props = 41; optional string cgroup_props_file = 42; repeated string cgroup_dump_controller = 43; optional string freeze_cgroup = 44; optional uint32 timeout = 45; optional bool tcp_skip_in_flight = 46; optional bool weak_sysctls = 47; optional bool lazy_pages = 48; optional int32 status_fd = 49; optional bool orphan_pts_master = 50; } message criu_dump_resp { optional bool restored = 1; } message criu_restore_resp { required int32 pid = 1; } message criu_notify { optional string script = 1; optional int32 pid = 2; } enum criu_req_type { EMPTY = 0; DUMP = 1; RESTORE = 2; CHECK = 3; PRE_DUMP = 4; PAGE_SERVER = 5; NOTIFY = 6; CPUINFO_DUMP = 7; CPUINFO_CHECK = 8; FEATURE_CHECK = 9; VERSION = 10; } /* * List of features which can queried via * CRIU_REQ_TYPE__FEATURE_CHECK */ message criu_features { optional bool mem_track = 1; optional bool lazy_pages = 2; } /* * Request -- each type corresponds to must-be-there * request arguments of respective type */ message criu_req { required criu_req_type type = 1; optional criu_opts opts = 2; optional bool notify_success = 3; /* * When set service won't close the connection but * will wait for more req-s to appear. Works not * for all request types. */ optional bool keep_open = 4; /* * 'features' can be used to query which features * are supported by the installed criu/kernel * via RPC. */ optional criu_features features = 5; } /* * Response -- it states whether the request was served * and additional request-specific information */ message criu_resp { required criu_req_type type = 1; required bool success = 2; optional criu_dump_resp dump = 3; optional criu_restore_resp restore = 4; optional criu_notify notify = 5; optional criu_page_server_info ps = 6; optional int32 cr_errno = 7; optional criu_features features = 8; optional string cr_errmsg = 9; optional criu_version version = 10; } /* Answer for criu_req_type.VERSION requests */ message criu_version { required int32 major = 1; required int32 minor = 2; optional string gitid = 3; optional int32 sublevel = 4; optional int32 extra = 5; optional string name = 6; } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/error.go000066400000000000000000000026241315410276000271010ustar00rootroot00000000000000package libcontainer import "io" // ErrorCode is the API error code type. type ErrorCode int // API error codes. const ( // Factory errors IdInUse ErrorCode = iota InvalidIdFormat // Container errors ContainerNotExists ContainerPaused ContainerNotStopped ContainerNotRunning ContainerNotPaused // Process errors NoProcessOps // Common errors ConfigInvalid ConsoleExists SystemError ) func (c ErrorCode) String() string { switch c { case IdInUse: return "Id already in use" case InvalidIdFormat: return "Invalid format" case ContainerPaused: return "Container paused" case ConfigInvalid: return "Invalid configuration" case SystemError: return "System error" case ContainerNotExists: return "Container does not exist" case ContainerNotStopped: return "Container is not stopped" case ContainerNotRunning: return "Container is not running" case ConsoleExists: return "Console exists for process" case ContainerNotPaused: return "Container is not paused" case NoProcessOps: return "No process operations" default: return "Unknown error" } } // Error is the API error type. type Error interface { error // Returns an error if it failed to write the detail of the Error to w. // The detail of the Error may include the error message and a // representation of the stack trace. Detail(w io.Writer) error // Returns the error code for this error. Code() ErrorCode } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/factory.go000066400000000000000000000026371315410276000274230ustar00rootroot00000000000000package libcontainer import ( "github.com/opencontainers/runc/libcontainer/configs" ) type Factory interface { // Creates a new container with the given id and starts the initial process inside it. // id must be a string containing only letters, digits and underscores and must contain // between 1 and 1024 characters, inclusive. // // The id must not already be in use by an existing container. Containers created using // a factory with the same path (and filesystem) must have distinct ids. // // Returns the new container with a running process. // // errors: // IdInUse - id is already in use by a container // InvalidIdFormat - id has incorrect format // ConfigInvalid - config is invalid // Systemerror - System error // // On error, any partially created container parts are cleaned up (the operation is atomic). Create(id string, config *configs.Config) (Container, error) // Load takes an ID for an existing container and returns the container information // from the state. This presents a read only view of the container. // // errors: // Path does not exist // System error Load(id string) (Container, error) // StartInitialization is an internal API to libcontainer used during the reexec of the // container. // // Errors: // Pipe connection error // System error StartInitialization() error // Type returns info string about factory type (e.g. lxc, libcontainer...) Type() string } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/factory_linux.go000066400000000000000000000223201315410276000306310ustar00rootroot00000000000000// +build linux package libcontainer import ( "encoding/json" "fmt" "os" "path/filepath" "regexp" "runtime/debug" "strconv" "github.com/docker/docker/pkg/mount" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/cgroups/fs" "github.com/opencontainers/runc/libcontainer/cgroups/rootless" "github.com/opencontainers/runc/libcontainer/cgroups/systemd" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs/validate" "github.com/opencontainers/runc/libcontainer/utils" "golang.org/x/sys/unix" ) const ( stateFilename = "state.json" execFifoFilename = "exec.fifo" ) var idRegex = regexp.MustCompile(`^[\w+-\.]+$`) // InitArgs returns an options func to configure a LinuxFactory with the // provided init binary path and arguments. func InitArgs(args ...string) func(*LinuxFactory) error { return func(l *LinuxFactory) (err error) { if len(args) > 0 { // Resolve relative paths to ensure that its available // after directory changes. if args[0], err = filepath.Abs(args[0]); err != nil { return newGenericError(err, ConfigInvalid) } } l.InitArgs = args return nil } } // SystemdCgroups is an options func to configure a LinuxFactory to return // containers that use systemd to create and manage cgroups. func SystemdCgroups(l *LinuxFactory) error { l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager { return &systemd.Manager{ Cgroups: config, Paths: paths, } } return nil } // Cgroupfs is an options func to configure a LinuxFactory to return // containers that use the native cgroups filesystem implementation to // create and manage cgroups. func Cgroupfs(l *LinuxFactory) error { l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager { return &fs.Manager{ Cgroups: config, Paths: paths, } } return nil } // RootlessCgroups is an options func to configure a LinuxFactory to // return containers that use the "rootless" cgroup manager, which will // fail to do any operations not possible to do with an unprivileged user. // It should only be used in conjunction with rootless containers. func RootlessCgroups(l *LinuxFactory) error { l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager { return &rootless.Manager{ Cgroups: config, Paths: paths, } } return nil } // TmpfsRoot is an option func to mount LinuxFactory.Root to tmpfs. func TmpfsRoot(l *LinuxFactory) error { mounted, err := mount.Mounted(l.Root) if err != nil { return err } if !mounted { if err := unix.Mount("tmpfs", l.Root, "tmpfs", 0, ""); err != nil { return err } } return nil } // CriuPath returns an option func to configure a LinuxFactory with the // provided criupath func CriuPath(criupath string) func(*LinuxFactory) error { return func(l *LinuxFactory) error { l.CriuPath = criupath return nil } } // New returns a linux based container factory based in the root directory and // configures the factory with the provided option funcs. func New(root string, options ...func(*LinuxFactory) error) (Factory, error) { if root != "" { if err := os.MkdirAll(root, 0700); err != nil { return nil, newGenericError(err, SystemError) } } l := &LinuxFactory{ Root: root, InitArgs: []string{"/proc/self/exe", "init"}, Validator: validate.New(), CriuPath: "criu", } Cgroupfs(l) for _, opt := range options { if err := opt(l); err != nil { return nil, err } } return l, nil } // LinuxFactory implements the default factory interface for linux based systems. type LinuxFactory struct { // Root directory for the factory to store state. Root string // InitArgs are arguments for calling the init responsibilities for spawning // a container. InitArgs []string // CriuPath is the path to the criu binary used for checkpoint and restore of // containers. CriuPath string // Validator provides validation to container configurations. Validator validate.Validator // NewCgroupsManager returns an initialized cgroups manager for a single container. NewCgroupsManager func(config *configs.Cgroup, paths map[string]string) cgroups.Manager } func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) { if l.Root == "" { return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid) } if err := l.validateID(id); err != nil { return nil, err } if err := l.Validator.Validate(config); err != nil { return nil, newGenericError(err, ConfigInvalid) } containerRoot := filepath.Join(l.Root, id) if _, err := os.Stat(containerRoot); err == nil { return nil, newGenericError(fmt.Errorf("container with id exists: %v", id), IdInUse) } else if !os.IsNotExist(err) { return nil, newGenericError(err, SystemError) } if err := os.MkdirAll(containerRoot, 0711); err != nil { return nil, newGenericError(err, SystemError) } if err := os.Chown(containerRoot, unix.Geteuid(), unix.Getegid()); err != nil { return nil, newGenericError(err, SystemError) } if config.Rootless { RootlessCgroups(l) } c := &linuxContainer{ id: id, root: containerRoot, config: config, initArgs: l.InitArgs, criuPath: l.CriuPath, cgroupManager: l.NewCgroupsManager(config.Cgroups, nil), } c.state = &stoppedState{c: c} return c, nil } func (l *LinuxFactory) Load(id string) (Container, error) { if l.Root == "" { return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid) } containerRoot := filepath.Join(l.Root, id) state, err := l.loadState(containerRoot, id) if err != nil { return nil, err } r := &nonChildProcess{ processPid: state.InitProcessPid, processStartTime: state.InitProcessStartTime, fds: state.ExternalDescriptors, } // We have to use the RootlessManager. if state.Rootless { RootlessCgroups(l) } c := &linuxContainer{ initProcess: r, initProcessStartTime: state.InitProcessStartTime, id: id, config: &state.Config, initArgs: l.InitArgs, criuPath: l.CriuPath, cgroupManager: l.NewCgroupsManager(state.Config.Cgroups, state.CgroupPaths), root: containerRoot, created: state.Created, } c.state = &loadedState{c: c} if err := c.refreshState(); err != nil { return nil, err } return c, nil } func (l *LinuxFactory) Type() string { return "libcontainer" } // StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state // This is a low level implementation detail of the reexec and should not be consumed externally func (l *LinuxFactory) StartInitialization() (err error) { var ( pipefd, fifofd int consoleSocket *os.File envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE") envFifoFd = os.Getenv("_LIBCONTAINER_FIFOFD") envConsole = os.Getenv("_LIBCONTAINER_CONSOLE") ) // Get the INITPIPE. pipefd, err = strconv.Atoi(envInitPipe) if err != nil { return fmt.Errorf("unable to convert _LIBCONTAINER_INITPIPE=%s to int: %s", envInitPipe, err) } var ( pipe = os.NewFile(uintptr(pipefd), "pipe") it = initType(os.Getenv("_LIBCONTAINER_INITTYPE")) ) defer pipe.Close() // Only init processes have FIFOFD. fifofd = -1 if it == initStandard { if fifofd, err = strconv.Atoi(envFifoFd); err != nil { return fmt.Errorf("unable to convert _LIBCONTAINER_FIFOFD=%s to int: %s", envFifoFd, err) } } if envConsole != "" { console, err := strconv.Atoi(envConsole) if err != nil { return fmt.Errorf("unable to convert _LIBCONTAINER_CONSOLE=%s to int: %s", envConsole, err) } consoleSocket = os.NewFile(uintptr(console), "console-socket") defer consoleSocket.Close() } // clear the current process's environment to clean any libcontainer // specific env vars. os.Clearenv() defer func() { // We have an error during the initialization of the container's init, // send it back to the parent process in the form of an initError. if werr := utils.WriteJSON(pipe, syncT{procError}); werr != nil { fmt.Fprintln(os.Stderr, err) return } if werr := utils.WriteJSON(pipe, newSystemError(err)); werr != nil { fmt.Fprintln(os.Stderr, err) return } }() defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic from initialization: %v, %v", e, string(debug.Stack())) } }() i, err := newContainerInit(it, pipe, consoleSocket, fifofd) if err != nil { return err } // If Init succeeds, syscall.Exec will not return, hence none of the defers will be called. return i.Init() } func (l *LinuxFactory) loadState(root, id string) (*State, error) { f, err := os.Open(filepath.Join(root, stateFilename)) if err != nil { if os.IsNotExist(err) { return nil, newGenericError(fmt.Errorf("container %q does not exist", id), ContainerNotExists) } return nil, newGenericError(err, SystemError) } defer f.Close() var state *State if err := json.NewDecoder(f).Decode(&state); err != nil { return nil, newGenericError(err, SystemError) } return state, nil } func (l *LinuxFactory) validateID(id string) error { if !idRegex.MatchString(id) { return newGenericError(fmt.Errorf("invalid id format: %v", id), InvalidIdFormat) } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/generic_error.go000066400000000000000000000037421315410276000305770ustar00rootroot00000000000000package libcontainer import ( "fmt" "io" "text/template" "time" "github.com/opencontainers/runc/libcontainer/stacktrace" ) var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} Code: {{.ECode}} {{if .Message }} Message: {{.Message}} {{end}} Frames:{{range $i, $frame := .Stack.Frames}} --- {{$i}}: {{$frame.Function}} Package: {{$frame.Package}} File: {{$frame.File}}@{{$frame.Line}}{{end}} `)) func newGenericError(err error, c ErrorCode) Error { if le, ok := err.(Error); ok { return le } gerr := &genericError{ Timestamp: time.Now(), Err: err, ECode: c, Stack: stacktrace.Capture(1), } if err != nil { gerr.Message = err.Error() } return gerr } func newSystemError(err error) Error { return createSystemError(err, "") } func newSystemErrorWithCausef(err error, cause string, v ...interface{}) Error { return createSystemError(err, fmt.Sprintf(cause, v...)) } func newSystemErrorWithCause(err error, cause string) Error { return createSystemError(err, cause) } // createSystemError creates the specified error with the correct number of // stack frames skipped. This is only to be called by the other functions for // formatting the error. func createSystemError(err error, cause string) Error { gerr := &genericError{ Timestamp: time.Now(), Err: err, ECode: SystemError, Cause: cause, Stack: stacktrace.Capture(2), } if err != nil { gerr.Message = err.Error() } return gerr } type genericError struct { Timestamp time.Time ECode ErrorCode Err error `json:"-"` Cause string Message string Stack stacktrace.Stacktrace } func (e *genericError) Error() string { if e.Cause == "" { return e.Message } frame := e.Stack.Frames[0] return fmt.Sprintf("%s:%d: %s caused %q", frame.File, frame.Line, e.Cause, e.Message) } func (e *genericError) Code() ErrorCode { return e.ECode } func (e *genericError) Detail(w io.Writer) error { return errorTemplate.Execute(w, e) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/init_linux.go000066400000000000000000000340521315410276000301320ustar00rootroot00000000000000// +build linux package libcontainer import ( "encoding/json" "fmt" "io" "net" "os" "strings" "syscall" // only for Errno "unsafe" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/user" "github.com/opencontainers/runc/libcontainer/utils" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" ) type initType string const ( initSetns initType = "setns" initStandard initType = "standard" ) type pid struct { Pid int `json:"pid"` PidFirstChild int `json:"pid_first"` } // network is an internal struct used to setup container networks. type network struct { configs.Network // TempVethPeerName is a unique temporary veth peer name that was placed into // the container's namespace. TempVethPeerName string `json:"temp_veth_peer_name"` } // initConfig is used for transferring parameters from Exec() to Init() type initConfig struct { Args []string `json:"args"` Env []string `json:"env"` Cwd string `json:"cwd"` Capabilities *configs.Capabilities `json:"capabilities"` ProcessLabel string `json:"process_label"` AppArmorProfile string `json:"apparmor_profile"` NoNewPrivileges bool `json:"no_new_privileges"` User string `json:"user"` AdditionalGroups []string `json:"additional_groups"` Config *configs.Config `json:"config"` Networks []*network `json:"network"` PassedFilesCount int `json:"passed_files_count"` ContainerId string `json:"containerid"` Rlimits []configs.Rlimit `json:"rlimits"` CreateConsole bool `json:"create_console"` Rootless bool `json:"rootless"` } type initer interface { Init() error } func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd int) (initer, error) { var config *initConfig if err := json.NewDecoder(pipe).Decode(&config); err != nil { return nil, err } if err := populateProcessEnvironment(config.Env); err != nil { return nil, err } switch t { case initSetns: return &linuxSetnsInit{ pipe: pipe, consoleSocket: consoleSocket, config: config, }, nil case initStandard: return &linuxStandardInit{ pipe: pipe, consoleSocket: consoleSocket, parentPid: unix.Getppid(), config: config, fifoFd: fifoFd, }, nil } return nil, fmt.Errorf("unknown init type %q", t) } // populateProcessEnvironment loads the provided environment variables into the // current processes's environment. func populateProcessEnvironment(env []string) error { for _, pair := range env { p := strings.SplitN(pair, "=", 2) if len(p) < 2 { return fmt.Errorf("invalid environment '%v'", pair) } if err := os.Setenv(p[0], p[1]); err != nil { return err } } return nil } // finalizeNamespace drops the caps, sets the correct user // and working dir, and closes any leaked file descriptors // before executing the command inside the namespace func finalizeNamespace(config *initConfig) error { // Ensure that all unwanted fds we may have accidentally // inherited are marked close-on-exec so they stay out of the // container if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil { return err } capabilities := &configs.Capabilities{} if config.Capabilities != nil { capabilities = config.Capabilities } else if config.Config.Capabilities != nil { capabilities = config.Config.Capabilities } w, err := newContainerCapList(capabilities) if err != nil { return err } // drop capabilities in bounding set before changing user if err := w.ApplyBoundingSet(); err != nil { return err } // preserve existing capabilities while we change users if err := system.SetKeepCaps(); err != nil { return err } if err := setupUser(config); err != nil { return err } if err := system.ClearKeepCaps(); err != nil { return err } if err := w.ApplyCaps(); err != nil { return err } if config.Cwd != "" { if err := unix.Chdir(config.Cwd); err != nil { return fmt.Errorf("chdir to cwd (%q) set in config.json failed: %v", config.Cwd, err) } } return nil } // setupConsole sets up the console from inside the container, and sends the // master pty fd to the config.Pipe (using cmsg). This is done to ensure that // consoles are scoped to a container properly (see runc#814 and the many // issues related to that). This has to be run *after* we've pivoted to the new // rootfs (and the users' configuration is entirely set up). func setupConsole(socket *os.File, config *initConfig, mount bool) error { defer socket.Close() // At this point, /dev/ptmx points to something that we would expect. We // used to change the owner of the slave path, but since the /dev/pts mount // can have gid=X set (at the users' option). So touching the owner of the // slave PTY is not necessary, as the kernel will handle that for us. Note // however, that setupUser (specifically fixStdioPermissions) *will* change // the UID owner of the console to be the user the process will run as (so // they can actually control their console). console, err := newConsole() if err != nil { return err } // After we return from here, we don't need the console anymore. defer console.Close() linuxConsole, ok := console.(*linuxConsole) if !ok { return fmt.Errorf("failed to cast console to *linuxConsole") } // Mount the console inside our rootfs. if mount { if err := linuxConsole.mount(); err != nil { return err } } // While we can access console.master, using the API is a good idea. if err := utils.SendFd(socket, linuxConsole.File()); err != nil { return err } // Now, dup over all the things. return linuxConsole.dupStdio() } // syncParentReady sends to the given pipe a JSON payload which indicates that // the init is ready to Exec the child process. It then waits for the parent to // indicate that it is cleared to Exec. func syncParentReady(pipe io.ReadWriter) error { // Tell parent. if err := writeSync(pipe, procReady); err != nil { return err } // Wait for parent to give the all-clear. if err := readSync(pipe, procRun); err != nil { return err } return nil } // syncParentHooks sends to the given pipe a JSON payload which indicates that // the parent should execute pre-start hooks. It then waits for the parent to // indicate that it is cleared to resume. func syncParentHooks(pipe io.ReadWriter) error { // Tell parent. if err := writeSync(pipe, procHooks); err != nil { return err } // Wait for parent to give the all-clear. if err := readSync(pipe, procResume); err != nil { return err } return nil } // setupUser changes the groups, gid, and uid for the user inside the container func setupUser(config *initConfig) error { // Set up defaults. defaultExecUser := user.ExecUser{ Uid: 0, Gid: 0, Home: "/", } passwdPath, err := user.GetPasswdPath() if err != nil { return err } groupPath, err := user.GetGroupPath() if err != nil { return err } execUser, err := user.GetExecUserPath(config.User, &defaultExecUser, passwdPath, groupPath) if err != nil { return err } var addGroups []int if len(config.AdditionalGroups) > 0 { addGroups, err = user.GetAdditionalGroupsPath(config.AdditionalGroups, groupPath) if err != nil { return err } } if config.Rootless { if execUser.Uid != 0 { return fmt.Errorf("cannot run as a non-root user in a rootless container") } if execUser.Gid != 0 { return fmt.Errorf("cannot run as a non-root group in a rootless container") } // We cannot set any additional groups in a rootless container and thus we // bail if the user asked us to do so. TODO: We currently can't do this // earlier, but if libcontainer.Process.User was typesafe this might work. if len(addGroups) > 0 { return fmt.Errorf("cannot set any additional groups in a rootless container") } } // before we change to the container's user make sure that the processes STDIO // is correctly owned by the user that we are switching to. if err := fixStdioPermissions(config, execUser); err != nil { return err } // This isn't allowed in an unprivileged user namespace since Linux 3.19. // There's nothing we can do about /etc/group entries, so we silently // ignore setting groups here (since the user didn't explicitly ask us to // set the group). if !config.Rootless { suppGroups := append(execUser.Sgids, addGroups...) if err := unix.Setgroups(suppGroups); err != nil { return err } } if err := system.Setgid(execUser.Gid); err != nil { return err } if err := system.Setuid(execUser.Uid); err != nil { return err } // if we didn't get HOME already, set it based on the user's HOME if envHome := os.Getenv("HOME"); envHome == "" { if err := os.Setenv("HOME", execUser.Home); err != nil { return err } } return nil } // fixStdioPermissions fixes the permissions of PID 1's STDIO within the container to the specified user. // The ownership needs to match because it is created outside of the container and needs to be // localized. func fixStdioPermissions(config *initConfig, u *user.ExecUser) error { var null unix.Stat_t if err := unix.Stat("/dev/null", &null); err != nil { return err } for _, fd := range []uintptr{ os.Stdin.Fd(), os.Stderr.Fd(), os.Stdout.Fd(), } { var s unix.Stat_t if err := unix.Fstat(int(fd), &s); err != nil { return err } // Skip chown of /dev/null if it was used as one of the STDIO fds. if s.Rdev == null.Rdev { continue } // Skip chown if s.Gid is actually an unmapped gid in the host. While // this is a bit dodgy if it just so happens that the console _is_ // owned by overflow_gid, there's no way for us to disambiguate this as // a userspace program. if _, err := config.Config.HostGID(int(s.Gid)); err != nil { continue } // We only change the uid owner (as it is possible for the mount to // prefer a different gid, and there's no reason for us to change it). // The reason why we don't just leave the default uid=X mount setup is // that users expect to be able to actually use their console. Without // this code, you couldn't effectively run as a non-root user inside a // container and also have a console set up. if err := unix.Fchown(int(fd), u.Uid, int(s.Gid)); err != nil { return err } } return nil } // setupNetwork sets up and initializes any network interface inside the container. func setupNetwork(config *initConfig) error { for _, config := range config.Networks { strategy, err := getStrategy(config.Type) if err != nil { return err } if err := strategy.initialize(config); err != nil { return err } } return nil } func setupRoute(config *configs.Config) error { for _, config := range config.Routes { _, dst, err := net.ParseCIDR(config.Destination) if err != nil { return err } src := net.ParseIP(config.Source) if src == nil { return fmt.Errorf("Invalid source for route: %s", config.Source) } gw := net.ParseIP(config.Gateway) if gw == nil { return fmt.Errorf("Invalid gateway for route: %s", config.Gateway) } l, err := netlink.LinkByName(config.InterfaceName) if err != nil { return err } route := &netlink.Route{ Scope: netlink.SCOPE_UNIVERSE, Dst: dst, Src: src, Gw: gw, LinkIndex: l.Attrs().Index, } if err := netlink.RouteAdd(route); err != nil { return err } } return nil } func setupRlimits(limits []configs.Rlimit, pid int) error { for _, rlimit := range limits { if err := system.Prlimit(pid, rlimit.Type, unix.Rlimit{Max: rlimit.Hard, Cur: rlimit.Soft}); err != nil { return fmt.Errorf("error setting rlimit type %v: %v", rlimit.Type, err) } } return nil } const _P_PID = 1 type siginfo struct { si_signo int32 si_errno int32 si_code int32 // below here is a union; si_pid is the only field we use si_pid int32 // Pad to 128 bytes as detailed in blockUntilWaitable pad [96]byte } // isWaitable returns true if the process has exited false otherwise. // Its based off blockUntilWaitable in src/os/wait_waitid.go func isWaitable(pid int) (bool, error) { si := &siginfo{} _, _, e := unix.Syscall6(unix.SYS_WAITID, _P_PID, uintptr(pid), uintptr(unsafe.Pointer(si)), unix.WEXITED|unix.WNOWAIT|unix.WNOHANG, 0, 0) if e != 0 { return false, os.NewSyscallError("waitid", e) } return si.si_pid != 0, nil } // isNoChildren returns true if err represents a unix.ECHILD (formerly syscall.ECHILD) false otherwise func isNoChildren(err error) bool { switch err := err.(type) { case syscall.Errno: if err == unix.ECHILD { return true } case *os.SyscallError: if err.Err == unix.ECHILD { return true } } return false } // signalAllProcesses freezes then iterates over all the processes inside the // manager's cgroups sending the signal s to them. // If s is SIGKILL then it will wait for each process to exit. // For all other signals it will check if the process is ready to report its // exit status and only if it is will a wait be performed. func signalAllProcesses(m cgroups.Manager, s os.Signal) error { var procs []*os.Process if err := m.Freeze(configs.Frozen); err != nil { logrus.Warn(err) } pids, err := m.GetAllPids() if err != nil { m.Freeze(configs.Thawed) return err } for _, pid := range pids { p, err := os.FindProcess(pid) if err != nil { logrus.Warn(err) continue } procs = append(procs, p) if err := p.Signal(s); err != nil { logrus.Warn(err) } } if err := m.Freeze(configs.Thawed); err != nil { logrus.Warn(err) } for _, p := range procs { if s != unix.SIGKILL { if ok, err := isWaitable(p.Pid); err != nil { if !isNoChildren(err) { logrus.Warn("signalAllProcesses: ", p.Pid, err) } continue } else if !ok { // Not ready to report so don't wait continue } } if _, err := p.Wait(); err != nil { if !isNoChildren(err) { logrus.Warn("wait: ", err) } } } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/keys/000077500000000000000000000000001315410276000263705ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/keys/keyctl.go000066400000000000000000000021131315410276000302070ustar00rootroot00000000000000// +build linux package keys import ( "fmt" "strconv" "strings" "golang.org/x/sys/unix" ) type KeySerial uint32 func JoinSessionKeyring(name string) (KeySerial, error) { sessKeyId, err := unix.KeyctlJoinSessionKeyring(name) if err != nil { return 0, fmt.Errorf("could not create session key: %v", err) } return KeySerial(sessKeyId), nil } // ModKeyringPerm modifies permissions on a keyring by reading the current permissions, // anding the bits with the given mask (clearing permissions) and setting // additional permission bits func ModKeyringPerm(ringId KeySerial, mask, setbits uint32) error { dest, err := unix.KeyctlString(unix.KEYCTL_DESCRIBE, int(ringId)) if err != nil { return err } res := strings.Split(string(dest), ";") if len(res) < 5 { return fmt.Errorf("Destination buffer for key description is too small") } // parse permissions perm64, err := strconv.ParseUint(res[3], 16, 32) if err != nil { return err } perm := (uint32(perm64) & mask) | setbits if err := unix.KeyctlSetperm(int(ringId), perm); err != nil { return err } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/message_linux.go000066400000000000000000000036131315410276000306120ustar00rootroot00000000000000// +build linux package libcontainer import ( "github.com/vishvananda/netlink/nl" "golang.org/x/sys/unix" ) // list of known message types we want to send to bootstrap program // The number is randomly chosen to not conflict with known netlink types const ( InitMsg uint16 = 62000 CloneFlagsAttr uint16 = 27281 NsPathsAttr uint16 = 27282 UidmapAttr uint16 = 27283 GidmapAttr uint16 = 27284 SetgroupAttr uint16 = 27285 OomScoreAdjAttr uint16 = 27286 RootlessAttr uint16 = 27287 ) type Int32msg struct { Type uint16 Value uint32 } // Serialize serializes the message. // Int32msg has the following representation // | nlattr len | nlattr type | // | uint32 value | func (msg *Int32msg) Serialize() []byte { buf := make([]byte, msg.Len()) native := nl.NativeEndian() native.PutUint16(buf[0:2], uint16(msg.Len())) native.PutUint16(buf[2:4], msg.Type) native.PutUint32(buf[4:8], msg.Value) return buf } func (msg *Int32msg) Len() int { return unix.NLA_HDRLEN + 4 } // Bytemsg has the following representation // | nlattr len | nlattr type | // | value | pad | type Bytemsg struct { Type uint16 Value []byte } func (msg *Bytemsg) Serialize() []byte { l := msg.Len() buf := make([]byte, (l+unix.NLA_ALIGNTO-1) & ^(unix.NLA_ALIGNTO-1)) native := nl.NativeEndian() native.PutUint16(buf[0:2], uint16(l)) native.PutUint16(buf[2:4], msg.Type) copy(buf[4:], msg.Value) return buf } func (msg *Bytemsg) Len() int { return unix.NLA_HDRLEN + len(msg.Value) + 1 // null-terminated } type Boolmsg struct { Type uint16 Value bool } func (msg *Boolmsg) Serialize() []byte { buf := make([]byte, msg.Len()) native := nl.NativeEndian() native.PutUint16(buf[0:2], uint16(msg.Len())) native.PutUint16(buf[2:4], msg.Type) if msg.Value { buf[4] = 1 } else { buf[4] = 0 } return buf } func (msg *Boolmsg) Len() int { return unix.NLA_HDRLEN + 1 } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/network_linux.go000066400000000000000000000146701315410276000306640ustar00rootroot00000000000000// +build linux package libcontainer import ( "fmt" "io/ioutil" "net" "path/filepath" "strconv" "strings" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/utils" "github.com/vishvananda/netlink" ) var strategies = map[string]networkStrategy{ "veth": &veth{}, "loopback": &loopback{}, } // networkStrategy represents a specific network configuration for // a container's networking stack type networkStrategy interface { create(*network, int) error initialize(*network) error detach(*configs.Network) error attach(*configs.Network) error } // getStrategy returns the specific network strategy for the // provided type. func getStrategy(tpe string) (networkStrategy, error) { s, exists := strategies[tpe] if !exists { return nil, fmt.Errorf("unknown strategy type %q", tpe) } return s, nil } // Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo. func getNetworkInterfaceStats(interfaceName string) (*NetworkInterface, error) { out := &NetworkInterface{Name: interfaceName} // This can happen if the network runtime information is missing - possible if the // container was created by an old version of libcontainer. if interfaceName == "" { return out, nil } type netStatsPair struct { // Where to write the output. Out *uint64 // The network stats file to read. File string } // Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container. netStats := []netStatsPair{ {Out: &out.RxBytes, File: "tx_bytes"}, {Out: &out.RxPackets, File: "tx_packets"}, {Out: &out.RxErrors, File: "tx_errors"}, {Out: &out.RxDropped, File: "tx_dropped"}, {Out: &out.TxBytes, File: "rx_bytes"}, {Out: &out.TxPackets, File: "rx_packets"}, {Out: &out.TxErrors, File: "rx_errors"}, {Out: &out.TxDropped, File: "rx_dropped"}, } for _, netStat := range netStats { data, err := readSysfsNetworkStats(interfaceName, netStat.File) if err != nil { return nil, err } *(netStat.Out) = data } return out, nil } // Reads the specified statistics available under /sys/class/net//statistics func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) { data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile)) if err != nil { return 0, err } return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) } // loopback is a network strategy that provides a basic loopback device type loopback struct { } func (l *loopback) create(n *network, nspid int) error { return nil } func (l *loopback) initialize(config *network) error { return netlink.LinkSetUp(&netlink.Device{LinkAttrs: netlink.LinkAttrs{Name: "lo"}}) } func (l *loopback) attach(n *configs.Network) (err error) { return nil } func (l *loopback) detach(n *configs.Network) (err error) { return nil } // veth is a network strategy that uses a bridge and creates // a veth pair, one that is attached to the bridge on the host and the other // is placed inside the container's namespace type veth struct { } func (v *veth) detach(n *configs.Network) (err error) { return netlink.LinkSetMaster(&netlink.Device{LinkAttrs: netlink.LinkAttrs{Name: n.HostInterfaceName}}, nil) } // attach a container network interface to an external network func (v *veth) attach(n *configs.Network) (err error) { brl, err := netlink.LinkByName(n.Bridge) if err != nil { return err } br, ok := brl.(*netlink.Bridge) if !ok { return fmt.Errorf("Wrong device type %T", brl) } host, err := netlink.LinkByName(n.HostInterfaceName) if err != nil { return err } if err := netlink.LinkSetMaster(host, br); err != nil { return err } if err := netlink.LinkSetMTU(host, n.Mtu); err != nil { return err } if n.HairpinMode { if err := netlink.LinkSetHairpin(host, true); err != nil { return err } } if err := netlink.LinkSetUp(host); err != nil { return err } return nil } func (v *veth) create(n *network, nspid int) (err error) { tmpName, err := v.generateTempPeerName() if err != nil { return err } n.TempVethPeerName = tmpName if n.Bridge == "" { return fmt.Errorf("bridge is not specified") } veth := &netlink.Veth{ LinkAttrs: netlink.LinkAttrs{ Name: n.HostInterfaceName, TxQLen: n.TxQueueLen, }, PeerName: n.TempVethPeerName, } if err := netlink.LinkAdd(veth); err != nil { return err } defer func() { if err != nil { netlink.LinkDel(veth) } }() if err := v.attach(&n.Network); err != nil { return err } child, err := netlink.LinkByName(n.TempVethPeerName) if err != nil { return err } return netlink.LinkSetNsPid(child, nspid) } func (v *veth) generateTempPeerName() (string, error) { return utils.GenerateRandomName("veth", 7) } func (v *veth) initialize(config *network) error { peer := config.TempVethPeerName if peer == "" { return fmt.Errorf("peer is not specified") } child, err := netlink.LinkByName(peer) if err != nil { return err } if err := netlink.LinkSetDown(child); err != nil { return err } if err := netlink.LinkSetName(child, config.Name); err != nil { return err } // get the interface again after we changed the name as the index also changes. if child, err = netlink.LinkByName(config.Name); err != nil { return err } if config.MacAddress != "" { mac, err := net.ParseMAC(config.MacAddress) if err != nil { return err } if err := netlink.LinkSetHardwareAddr(child, mac); err != nil { return err } } ip, err := netlink.ParseAddr(config.Address) if err != nil { return err } if err := netlink.AddrAdd(child, ip); err != nil { return err } if config.IPv6Address != "" { ip6, err := netlink.ParseAddr(config.IPv6Address) if err != nil { return err } if err := netlink.AddrAdd(child, ip6); err != nil { return err } } if err := netlink.LinkSetMTU(child, config.Mtu); err != nil { return err } if err := netlink.LinkSetUp(child); err != nil { return err } if config.Gateway != "" { gw := net.ParseIP(config.Gateway) if err := netlink.RouteAdd(&netlink.Route{ Scope: netlink.SCOPE_UNIVERSE, LinkIndex: child.Attrs().Index, Gw: gw, }); err != nil { return err } } if config.IPv6Gateway != "" { gw := net.ParseIP(config.IPv6Gateway) if err := netlink.RouteAdd(&netlink.Route{ Scope: netlink.SCOPE_UNIVERSE, LinkIndex: child.Attrs().Index, Gw: gw, }); err != nil { return err } } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/notify_linux.go000066400000000000000000000041211315410276000304710ustar00rootroot00000000000000// +build linux package libcontainer import ( "fmt" "io/ioutil" "os" "path/filepath" "golang.org/x/sys/unix" ) const oomCgroupName = "memory" type PressureLevel uint const ( LowPressure PressureLevel = iota MediumPressure CriticalPressure ) func registerMemoryEvent(cgDir string, evName string, arg string) (<-chan struct{}, error) { evFile, err := os.Open(filepath.Join(cgDir, evName)) if err != nil { return nil, err } fd, err := unix.Eventfd(0, unix.EFD_CLOEXEC) if err != nil { evFile.Close() return nil, err } eventfd := os.NewFile(uintptr(fd), "eventfd") eventControlPath := filepath.Join(cgDir, "cgroup.event_control") data := fmt.Sprintf("%d %d %s", eventfd.Fd(), evFile.Fd(), arg) if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil { eventfd.Close() evFile.Close() return nil, err } ch := make(chan struct{}) go func() { defer func() { eventfd.Close() evFile.Close() close(ch) }() buf := make([]byte, 8) for { if _, err := eventfd.Read(buf); err != nil { return } // When a cgroup is destroyed, an event is sent to eventfd. // So if the control path is gone, return instead of notifying. if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) { return } ch <- struct{}{} } }() return ch, nil } // notifyOnOOM returns channel on which you can expect event about OOM, // if process died without OOM this channel will be closed. func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { dir := paths[oomCgroupName] if dir == "" { return nil, fmt.Errorf("path %q missing", oomCgroupName) } return registerMemoryEvent(dir, "memory.oom_control", "") } func notifyMemoryPressure(paths map[string]string, level PressureLevel) (<-chan struct{}, error) { dir := paths[oomCgroupName] if dir == "" { return nil, fmt.Errorf("path %q missing", oomCgroupName) } if level > CriticalPressure { return nil, fmt.Errorf("invalid pressure level %d", level) } levelStr := []string{"low", "medium", "critical"}[level] return registerMemoryEvent(dir, "memory.pressure_level", levelStr) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/process.go000066400000000000000000000060011315410276000274170ustar00rootroot00000000000000package libcontainer import ( "fmt" "io" "math" "os" "github.com/opencontainers/runc/libcontainer/configs" ) type processOperations interface { wait() (*os.ProcessState, error) signal(sig os.Signal) error pid() int } // Process specifies the configuration and IO for a process inside // a container. type Process struct { // The command to be run followed by any arguments. Args []string // Env specifies the environment variables for the process. Env []string // User will set the uid and gid of the executing process running inside the container // local to the container's user and group configuration. User string // AdditionalGroups specifies the gids that should be added to supplementary groups // in addition to those that the user belongs to. AdditionalGroups []string // Cwd will change the processes current working directory inside the container's rootfs. Cwd string // Stdin is a pointer to a reader which provides the standard input stream. Stdin io.Reader // Stdout is a pointer to a writer which receives the standard output stream. Stdout io.Writer // Stderr is a pointer to a writer which receives the standard error stream. Stderr io.Writer // ExtraFiles specifies additional open files to be inherited by the container ExtraFiles []*os.File // Capabilities specify the capabilities to keep when executing the process inside the container // All capabilities not specified will be dropped from the processes capability mask Capabilities *configs.Capabilities // AppArmorProfile specifies the profile to apply to the process and is // changed at the time the process is execed AppArmorProfile string // Label specifies the label to apply to the process. It is commonly used by selinux Label string // NoNewPrivileges controls whether processes can gain additional privileges. NoNewPrivileges *bool // Rlimits specifies the resource limits, such as max open files, to set in the container // If Rlimits are not set, the container will inherit rlimits from the parent process Rlimits []configs.Rlimit // ConsoleSocket provides the masterfd console. ConsoleSocket *os.File ops processOperations } // Wait waits for the process to exit. // Wait releases any resources associated with the Process func (p Process) Wait() (*os.ProcessState, error) { if p.ops == nil { return nil, newGenericError(fmt.Errorf("invalid process"), NoProcessOps) } return p.ops.wait() } // Pid returns the process ID func (p Process) Pid() (int, error) { // math.MinInt32 is returned here, because it's invalid value // for the kill() system call. if p.ops == nil { return math.MinInt32, newGenericError(fmt.Errorf("invalid process"), NoProcessOps) } return p.ops.pid(), nil } // Signal sends a signal to the Process. func (p Process) Signal(sig os.Signal) error { if p.ops == nil { return newGenericError(fmt.Errorf("invalid process"), NoProcessOps) } return p.ops.signal(sig) } // IO holds the process's STDIO type IO struct { Stdin io.WriteCloser Stdout io.ReadCloser Stderr io.ReadCloser } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/process_linux.go000066400000000000000000000340701315410276000306450ustar00rootroot00000000000000// +build linux package libcontainer import ( "encoding/json" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "strconv" "syscall" // only for Signal "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/utils" "golang.org/x/sys/unix" ) type parentProcess interface { // pid returns the pid for the running process. pid() int // start starts the process execution. start() error // send a SIGKILL to the process and wait for the exit. terminate() error // wait waits on the process returning the process state. wait() (*os.ProcessState, error) // startTime returns the process start time. startTime() (uint64, error) signal(os.Signal) error externalDescriptors() []string setExternalDescriptors(fds []string) } type setnsProcess struct { cmd *exec.Cmd parentPipe *os.File childPipe *os.File cgroupPaths map[string]string config *initConfig fds []string process *Process bootstrapData io.Reader } func (p *setnsProcess) startTime() (uint64, error) { stat, err := system.Stat(p.pid()) return stat.StartTime, err } func (p *setnsProcess) signal(sig os.Signal) error { s, ok := sig.(syscall.Signal) if !ok { return errors.New("os: unsupported signal type") } return unix.Kill(p.pid(), s) } func (p *setnsProcess) start() (err error) { defer p.parentPipe.Close() err = p.cmd.Start() p.childPipe.Close() if err != nil { return newSystemErrorWithCause(err, "starting setns process") } if p.bootstrapData != nil { if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil { return newSystemErrorWithCause(err, "copying bootstrap data to pipe") } } if err = p.execSetns(); err != nil { return newSystemErrorWithCause(err, "executing setns process") } // We can't join cgroups if we're in a rootless container. if !p.config.Rootless && len(p.cgroupPaths) > 0 { if err := cgroups.EnterPid(p.cgroupPaths, p.pid()); err != nil { return newSystemErrorWithCausef(err, "adding pid %d to cgroups", p.pid()) } } // set rlimits, this has to be done here because we lose permissions // to raise the limits once we enter a user-namespace if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil { return newSystemErrorWithCause(err, "setting rlimits for process") } if err := utils.WriteJSON(p.parentPipe, p.config); err != nil { return newSystemErrorWithCause(err, "writing config to pipe") } ierr := parseSync(p.parentPipe, func(sync *syncT) error { switch sync.Type { case procReady: // This shouldn't happen. panic("unexpected procReady in setns") case procHooks: // This shouldn't happen. panic("unexpected procHooks in setns") default: return newSystemError(fmt.Errorf("invalid JSON payload from child")) } }) if err := unix.Shutdown(int(p.parentPipe.Fd()), unix.SHUT_WR); err != nil { return newSystemErrorWithCause(err, "calling shutdown on init pipe") } // Must be done after Shutdown so the child will exit and we can wait for it. if ierr != nil { p.wait() return ierr } return nil } // execSetns runs the process that executes C code to perform the setns calls // because setns support requires the C process to fork off a child and perform the setns // before the go runtime boots, we wait on the process to die and receive the child's pid // over the provided pipe. func (p *setnsProcess) execSetns() error { status, err := p.cmd.Process.Wait() if err != nil { p.cmd.Wait() return newSystemErrorWithCause(err, "waiting on setns process to finish") } if !status.Success() { p.cmd.Wait() return newSystemError(&exec.ExitError{ProcessState: status}) } var pid *pid if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil { p.cmd.Wait() return newSystemErrorWithCause(err, "reading pid from init pipe") } // Clean up the zombie parent process firstChildProcess, err := os.FindProcess(pid.PidFirstChild) if err != nil { return err } // Ignore the error in case the child has already been reaped for any reason _, _ = firstChildProcess.Wait() process, err := os.FindProcess(pid.Pid) if err != nil { return err } p.cmd.Process = process p.process.ops = p return nil } // terminate sends a SIGKILL to the forked process for the setns routine then waits to // avoid the process becoming a zombie. func (p *setnsProcess) terminate() error { if p.cmd.Process == nil { return nil } err := p.cmd.Process.Kill() if _, werr := p.wait(); err == nil { err = werr } return err } func (p *setnsProcess) wait() (*os.ProcessState, error) { err := p.cmd.Wait() // Return actual ProcessState even on Wait error return p.cmd.ProcessState, err } func (p *setnsProcess) pid() int { return p.cmd.Process.Pid } func (p *setnsProcess) externalDescriptors() []string { return p.fds } func (p *setnsProcess) setExternalDescriptors(newFds []string) { p.fds = newFds } type initProcess struct { cmd *exec.Cmd parentPipe *os.File childPipe *os.File config *initConfig manager cgroups.Manager container *linuxContainer fds []string process *Process bootstrapData io.Reader sharePidns bool } func (p *initProcess) pid() int { return p.cmd.Process.Pid } func (p *initProcess) externalDescriptors() []string { return p.fds } // execSetns runs the process that executes C code to perform the setns calls // because setns support requires the C process to fork off a child and perform the setns // before the go runtime boots, we wait on the process to die and receive the child's pid // over the provided pipe. // This is called by initProcess.start function func (p *initProcess) execSetns() error { status, err := p.cmd.Process.Wait() if err != nil { p.cmd.Wait() return err } if !status.Success() { p.cmd.Wait() return &exec.ExitError{ProcessState: status} } var pid *pid if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil { p.cmd.Wait() return err } // Clean up the zombie parent process firstChildProcess, err := os.FindProcess(pid.PidFirstChild) if err != nil { return err } // Ignore the error in case the child has already been reaped for any reason _, _ = firstChildProcess.Wait() process, err := os.FindProcess(pid.Pid) if err != nil { return err } p.cmd.Process = process p.process.ops = p return nil } func (p *initProcess) start() error { defer p.parentPipe.Close() err := p.cmd.Start() p.process.ops = p p.childPipe.Close() if err != nil { p.process.ops = nil return newSystemErrorWithCause(err, "starting init process command") } if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil { return newSystemErrorWithCause(err, "copying bootstrap data to pipe") } if err := p.execSetns(); err != nil { return newSystemErrorWithCause(err, "running exec setns process for init") } // Save the standard descriptor names before the container process // can potentially move them (e.g., via dup2()). If we don't do this now, // we won't know at checkpoint time which file descriptor to look up. fds, err := getPipeFds(p.pid()) if err != nil { return newSystemErrorWithCausef(err, "getting pipe fds for pid %d", p.pid()) } p.setExternalDescriptors(fds) // Do this before syncing with child so that no children can escape the // cgroup. We don't need to worry about not doing this and not being root // because we'd be using the rootless cgroup manager in that case. if err := p.manager.Apply(p.pid()); err != nil { return newSystemErrorWithCause(err, "applying cgroup configuration for process") } defer func() { if err != nil { // TODO: should not be the responsibility to call here p.manager.Destroy() } }() if err := p.createNetworkInterfaces(); err != nil { return newSystemErrorWithCause(err, "creating network interfaces") } if err := p.sendConfig(); err != nil { return newSystemErrorWithCause(err, "sending config to init process") } var ( sentRun bool sentResume bool ) ierr := parseSync(p.parentPipe, func(sync *syncT) error { switch sync.Type { case procReady: // set rlimits, this has to be done here because we lose permissions // to raise the limits once we enter a user-namespace if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil { return newSystemErrorWithCause(err, "setting rlimits for ready process") } // call prestart hooks if !p.config.Config.Namespaces.Contains(configs.NEWNS) { // Setup cgroup before prestart hook, so that the prestart hook could apply cgroup permissions. if err := p.manager.Set(p.config.Config); err != nil { return newSystemErrorWithCause(err, "setting cgroup config for ready process") } if p.config.Config.Hooks != nil { s := configs.HookState{ Version: p.container.config.Version, ID: p.container.id, Pid: p.pid(), Bundle: utils.SearchLabels(p.config.Config.Labels, "bundle"), } for i, hook := range p.config.Config.Hooks.Prestart { if err := hook.Run(s); err != nil { return newSystemErrorWithCausef(err, "running prestart hook %d", i) } } } } // Sync with child. if err := writeSync(p.parentPipe, procRun); err != nil { return newSystemErrorWithCause(err, "writing syncT 'run'") } sentRun = true case procHooks: // Setup cgroup before prestart hook, so that the prestart hook could apply cgroup permissions. if err := p.manager.Set(p.config.Config); err != nil { return newSystemErrorWithCause(err, "setting cgroup config for procHooks process") } if p.config.Config.Hooks != nil { s := configs.HookState{ Version: p.container.config.Version, ID: p.container.id, Pid: p.pid(), Bundle: utils.SearchLabels(p.config.Config.Labels, "bundle"), } for i, hook := range p.config.Config.Hooks.Prestart { if err := hook.Run(s); err != nil { return newSystemErrorWithCausef(err, "running prestart hook %d", i) } } } // Sync with child. if err := writeSync(p.parentPipe, procResume); err != nil { return newSystemErrorWithCause(err, "writing syncT 'resume'") } sentResume = true default: return newSystemError(fmt.Errorf("invalid JSON payload from child")) } return nil }) if !sentRun { return newSystemErrorWithCause(ierr, "container init") } if p.config.Config.Namespaces.Contains(configs.NEWNS) && !sentResume { return newSystemError(fmt.Errorf("could not synchronise after executing prestart hooks with container process")) } if err := unix.Shutdown(int(p.parentPipe.Fd()), unix.SHUT_WR); err != nil { return newSystemErrorWithCause(err, "shutting down init pipe") } // Must be done after Shutdown so the child will exit and we can wait for it. if ierr != nil { p.wait() return ierr } return nil } func (p *initProcess) wait() (*os.ProcessState, error) { err := p.cmd.Wait() if err != nil { return p.cmd.ProcessState, err } // we should kill all processes in cgroup when init is died if we use host PID namespace if p.sharePidns { signalAllProcesses(p.manager, unix.SIGKILL) } return p.cmd.ProcessState, nil } func (p *initProcess) terminate() error { if p.cmd.Process == nil { return nil } err := p.cmd.Process.Kill() if _, werr := p.wait(); err == nil { err = werr } return err } func (p *initProcess) startTime() (uint64, error) { stat, err := system.Stat(p.pid()) return stat.StartTime, err } func (p *initProcess) sendConfig() error { // send the config to the container's init process, we don't use JSON Encode // here because there might be a problem in JSON decoder in some cases, see: // https://github.com/docker/docker/issues/14203#issuecomment-174177790 return utils.WriteJSON(p.parentPipe, p.config) } func (p *initProcess) createNetworkInterfaces() error { for _, config := range p.config.Config.Networks { strategy, err := getStrategy(config.Type) if err != nil { return err } n := &network{ Network: *config, } if err := strategy.create(n, p.pid()); err != nil { return err } p.config.Networks = append(p.config.Networks, n) } return nil } func (p *initProcess) signal(sig os.Signal) error { s, ok := sig.(syscall.Signal) if !ok { return errors.New("os: unsupported signal type") } return unix.Kill(p.pid(), s) } func (p *initProcess) setExternalDescriptors(newFds []string) { p.fds = newFds } func getPipeFds(pid int) ([]string, error) { fds := make([]string, 3) dirPath := filepath.Join("/proc", strconv.Itoa(pid), "/fd") for i := 0; i < 3; i++ { // XXX: This breaks if the path is not a valid symlink (which can // happen in certain particularly unlucky mount namespace setups). f := filepath.Join(dirPath, strconv.Itoa(i)) target, err := os.Readlink(f) if err != nil { // Ignore permission errors, for rootless containers and other // non-dumpable processes. if we can't get the fd for a particular // file, there's not much we can do. if os.IsPermission(err) { continue } return fds, err } fds[i] = target } return fds, nil } // InitializeIO creates pipes for use with the process's stdio and returns the // opposite side for each. Do not use this if you want to have a pseudoterminal // set up for you by libcontainer (TODO: fix that too). // TODO: This is mostly unnecessary, and should be handled by clients. func (p *Process) InitializeIO(rootuid, rootgid int) (i *IO, err error) { var fds []uintptr i = &IO{} // cleanup in case of an error defer func() { if err != nil { for _, fd := range fds { unix.Close(int(fd)) } } }() // STDIN r, w, err := os.Pipe() if err != nil { return nil, err } fds = append(fds, r.Fd(), w.Fd()) p.Stdin, i.Stdin = r, w // STDOUT if r, w, err = os.Pipe(); err != nil { return nil, err } fds = append(fds, r.Fd(), w.Fd()) p.Stdout, i.Stdout = w, r // STDERR if r, w, err = os.Pipe(); err != nil { return nil, err } fds = append(fds, r.Fd(), w.Fd()) p.Stderr, i.Stderr = w, r // change ownership of the pipes incase we are in a user namespace for _, fd := range fds { if err := unix.Fchown(int(fd), rootuid, rootgid); err != nil { return nil, err } } return i, nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/restored_process.go000066400000000000000000000051621315410276000313350ustar00rootroot00000000000000// +build linux package libcontainer import ( "fmt" "os" "github.com/opencontainers/runc/libcontainer/system" ) func newRestoredProcess(pid int, fds []string) (*restoredProcess, error) { var ( err error ) proc, err := os.FindProcess(pid) if err != nil { return nil, err } stat, err := system.Stat(pid) if err != nil { return nil, err } return &restoredProcess{ proc: proc, processStartTime: stat.StartTime, fds: fds, }, nil } type restoredProcess struct { proc *os.Process processStartTime uint64 fds []string } func (p *restoredProcess) start() error { return newGenericError(fmt.Errorf("restored process cannot be started"), SystemError) } func (p *restoredProcess) pid() int { return p.proc.Pid } func (p *restoredProcess) terminate() error { err := p.proc.Kill() if _, werr := p.wait(); err == nil { err = werr } return err } func (p *restoredProcess) wait() (*os.ProcessState, error) { // TODO: how do we wait on the actual process? // maybe use --exec-cmd in criu st, err := p.proc.Wait() if err != nil { return nil, err } return st, nil } func (p *restoredProcess) startTime() (uint64, error) { return p.processStartTime, nil } func (p *restoredProcess) signal(s os.Signal) error { return p.proc.Signal(s) } func (p *restoredProcess) externalDescriptors() []string { return p.fds } func (p *restoredProcess) setExternalDescriptors(newFds []string) { p.fds = newFds } // nonChildProcess represents a process where the calling process is not // the parent process. This process is created when a factory loads a container from // a persisted state. type nonChildProcess struct { processPid int processStartTime uint64 fds []string } func (p *nonChildProcess) start() error { return newGenericError(fmt.Errorf("restored process cannot be started"), SystemError) } func (p *nonChildProcess) pid() int { return p.processPid } func (p *nonChildProcess) terminate() error { return newGenericError(fmt.Errorf("restored process cannot be terminated"), SystemError) } func (p *nonChildProcess) wait() (*os.ProcessState, error) { return nil, newGenericError(fmt.Errorf("restored process cannot be waited on"), SystemError) } func (p *nonChildProcess) startTime() (uint64, error) { return p.processStartTime, nil } func (p *nonChildProcess) signal(s os.Signal) error { proc, err := os.FindProcess(p.processPid) if err != nil { return err } return proc.Signal(s) } func (p *nonChildProcess) externalDescriptors() []string { return p.fds } func (p *nonChildProcess) setExternalDescriptors(newFds []string) { p.fds = newFds } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/rootfs_linux.go000066400000000000000000000562271315410276000305130ustar00rootroot00000000000000// +build linux package libcontainer import ( "fmt" "io" "io/ioutil" "os" "os/exec" "path" "path/filepath" "strings" "time" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/symlink" "github.com/mrunalp/fileutils" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/system" libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" "github.com/opencontainers/selinux/go-selinux/label" "golang.org/x/sys/unix" ) const defaultMountFlags = unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV // needsSetupDev returns true if /dev needs to be set up. func needsSetupDev(config *configs.Config) bool { for _, m := range config.Mounts { if m.Device == "bind" && libcontainerUtils.CleanPath(m.Destination) == "/dev" { return false } } return true } // prepareRootfs sets up the devices, mount points, and filesystems for use // inside a new mount namespace. It doesn't set anything as ro. You must call // finalizeRootfs after this function to finish setting up the rootfs. func prepareRootfs(pipe io.ReadWriter, config *configs.Config) (err error) { if err := prepareRoot(config); err != nil { return newSystemErrorWithCause(err, "preparing rootfs") } setupDev := needsSetupDev(config) for _, m := range config.Mounts { for _, precmd := range m.PremountCmds { if err := mountCmd(precmd); err != nil { return newSystemErrorWithCause(err, "running premount command") } } if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil { return newSystemErrorWithCausef(err, "mounting %q to rootfs %q at %q", m.Source, config.Rootfs, m.Destination) } for _, postcmd := range m.PostmountCmds { if err := mountCmd(postcmd); err != nil { return newSystemErrorWithCause(err, "running postmount command") } } } if setupDev { if err := createDevices(config); err != nil { return newSystemErrorWithCause(err, "creating device nodes") } if err := setupPtmx(config); err != nil { return newSystemErrorWithCause(err, "setting up ptmx") } if err := setupDevSymlinks(config.Rootfs); err != nil { return newSystemErrorWithCause(err, "setting up /dev symlinks") } } // Signal the parent to run the pre-start hooks. // The hooks are run after the mounts are setup, but before we switch to the new // root, so that the old root is still available in the hooks for any mount // manipulations. if err := syncParentHooks(pipe); err != nil { return err } // The reason these operations are done here rather than in finalizeRootfs // is because the console-handling code gets quite sticky if we have to set // up the console before doing the pivot_root(2). This is because the // Console API has to also work with the ExecIn case, which means that the // API must be able to deal with being inside as well as outside the // container. It's just cleaner to do this here (at the expense of the // operation not being perfectly split). if err := unix.Chdir(config.Rootfs); err != nil { return newSystemErrorWithCausef(err, "changing dir to %q", config.Rootfs) } if config.NoPivotRoot { err = msMoveRoot(config.Rootfs) } else { err = pivotRoot(config.Rootfs) } if err != nil { return newSystemErrorWithCause(err, "jailing process inside rootfs") } if setupDev { if err := reOpenDevNull(); err != nil { return newSystemErrorWithCause(err, "reopening /dev/null inside container") } } return nil } // finalizeRootfs sets anything to ro if necessary. You must call // prepareRootfs first. func finalizeRootfs(config *configs.Config) (err error) { // remount dev as ro if specified for _, m := range config.Mounts { if libcontainerUtils.CleanPath(m.Destination) == "/dev" { if m.Flags&unix.MS_RDONLY == unix.MS_RDONLY { if err := remountReadonly(m); err != nil { return newSystemErrorWithCausef(err, "remounting %q as readonly", m.Destination) } } break } } // set rootfs ( / ) as readonly if config.Readonlyfs { if err := setReadonly(); err != nil { return newSystemErrorWithCause(err, "setting rootfs as readonly") } } unix.Umask(0022) return nil } func mountCmd(cmd configs.Command) error { command := exec.Command(cmd.Path, cmd.Args[:]...) command.Env = cmd.Env command.Dir = cmd.Dir if out, err := command.CombinedOutput(); err != nil { return fmt.Errorf("%#v failed: %s: %v", cmd, string(out), err) } return nil } func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { var ( dest = m.Destination ) if !strings.HasPrefix(dest, rootfs) { dest = filepath.Join(rootfs, dest) } switch m.Device { case "proc", "sysfs": if err := os.MkdirAll(dest, 0755); err != nil { return err } // Selinux kernels do not support labeling of /proc or /sys return mountPropagate(m, rootfs, "") case "mqueue": if err := os.MkdirAll(dest, 0755); err != nil { return err } if err := mountPropagate(m, rootfs, mountLabel); err != nil { // older kernels do not support labeling of /dev/mqueue if err := mountPropagate(m, rootfs, ""); err != nil { return err } return label.SetFileLabel(dest, mountLabel) } return nil case "tmpfs": copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP tmpDir := "" stat, err := os.Stat(dest) if err != nil { if err := os.MkdirAll(dest, 0755); err != nil { return err } } if copyUp { tmpDir, err = ioutil.TempDir("/tmp", "runctmpdir") if err != nil { return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir") } defer os.RemoveAll(tmpDir) m.Destination = tmpDir } if err := mountPropagate(m, rootfs, mountLabel); err != nil { return err } if copyUp { if err := fileutils.CopyDirectory(dest, tmpDir); err != nil { errMsg := fmt.Errorf("tmpcopyup: failed to copy %s to %s: %v", dest, tmpDir, err) if err1 := unix.Unmount(tmpDir, unix.MNT_DETACH); err1 != nil { return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg) } return errMsg } if err := unix.Mount(tmpDir, dest, "", unix.MS_MOVE, ""); err != nil { errMsg := fmt.Errorf("tmpcopyup: failed to move mount %s to %s: %v", tmpDir, dest, err) if err1 := unix.Unmount(tmpDir, unix.MNT_DETACH); err1 != nil { return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg) } return errMsg } } if stat != nil { if err = os.Chmod(dest, stat.Mode()); err != nil { return err } } return nil case "bind": stat, err := os.Stat(m.Source) if err != nil { // error out if the source of a bind mount does not exist as we will be // unable to bind anything to it. return err } // ensure that the destination of the bind mount is resolved of symlinks at mount time because // any previous mounts can invalidate the next mount's destination. // this can happen when a user specifies mounts within other mounts to cause breakouts or other // evil stuff to try to escape the container's rootfs. if dest, err = symlink.FollowSymlinkInScope(dest, rootfs); err != nil { return err } if err := checkMountDestination(rootfs, dest); err != nil { return err } // update the mount with the correct dest after symlinks are resolved. m.Destination = dest if err := createIfNotExists(dest, stat.IsDir()); err != nil { return err } if err := mountPropagate(m, rootfs, mountLabel); err != nil { return err } // bind mount won't change mount options, we need remount to make mount options effective. // first check that we have non-default options required before attempting a remount if m.Flags&^(unix.MS_REC|unix.MS_REMOUNT|unix.MS_BIND) != 0 { // only remount if unique mount options are set if err := remount(m, rootfs); err != nil { return err } } if m.Relabel != "" { if err := label.Validate(m.Relabel); err != nil { return err } shared := label.IsShared(m.Relabel) if err := label.Relabel(m.Source, mountLabel, shared); err != nil { return err } } case "cgroup": binds, err := getCgroupMounts(m) if err != nil { return err } var merged []string for _, b := range binds { ss := filepath.Base(b.Destination) if strings.Contains(ss, ",") { merged = append(merged, ss) } } tmpfs := &configs.Mount{ Source: "tmpfs", Device: "tmpfs", Destination: m.Destination, Flags: defaultMountFlags, Data: "mode=755", PropagationFlags: m.PropagationFlags, } if err := mountToRootfs(tmpfs, rootfs, mountLabel); err != nil { return err } for _, b := range binds { if err := mountToRootfs(b, rootfs, mountLabel); err != nil { return err } } for _, mc := range merged { for _, ss := range strings.Split(mc, ",") { // symlink(2) is very dumb, it will just shove the path into // the link and doesn't do any checks or relative path // conversion. Also, don't error out if the cgroup already exists. if err := os.Symlink(mc, filepath.Join(rootfs, m.Destination, ss)); err != nil && !os.IsExist(err) { return err } } } if m.Flags&unix.MS_RDONLY != 0 { // remount cgroup root as readonly mcgrouproot := &configs.Mount{ Source: m.Destination, Device: "bind", Destination: m.Destination, Flags: defaultMountFlags | unix.MS_RDONLY | unix.MS_BIND, } if err := remount(mcgrouproot, rootfs); err != nil { return err } } default: // ensure that the destination of the mount is resolved of symlinks at mount time because // any previous mounts can invalidate the next mount's destination. // this can happen when a user specifies mounts within other mounts to cause breakouts or other // evil stuff to try to escape the container's rootfs. var err error if dest, err = symlink.FollowSymlinkInScope(dest, rootfs); err != nil { return err } if err := checkMountDestination(rootfs, dest); err != nil { return err } // update the mount with the correct dest after symlinks are resolved. m.Destination = dest if err := os.MkdirAll(dest, 0755); err != nil { return err } return mountPropagate(m, rootfs, mountLabel) } return nil } func getCgroupMounts(m *configs.Mount) ([]*configs.Mount, error) { mounts, err := cgroups.GetCgroupMounts(false) if err != nil { return nil, err } cgroupPaths, err := cgroups.ParseCgroupFile("/proc/self/cgroup") if err != nil { return nil, err } var binds []*configs.Mount for _, mm := range mounts { dir, err := mm.GetOwnCgroup(cgroupPaths) if err != nil { return nil, err } relDir, err := filepath.Rel(mm.Root, dir) if err != nil { return nil, err } binds = append(binds, &configs.Mount{ Device: "bind", Source: filepath.Join(mm.Mountpoint, relDir), Destination: filepath.Join(m.Destination, filepath.Base(mm.Mountpoint)), Flags: unix.MS_BIND | unix.MS_REC | m.Flags, PropagationFlags: m.PropagationFlags, }) } return binds, nil } // checkMountDestination checks to ensure that the mount destination is not over the top of /proc. // dest is required to be an abs path and have any symlinks resolved before calling this function. func checkMountDestination(rootfs, dest string) error { invalidDestinations := []string{ "/proc", } // White list, it should be sub directories of invalid destinations validDestinations := []string{ // These entries can be bind mounted by files emulated by fuse, // so commands like top, free displays stats in container. "/proc/cpuinfo", "/proc/diskstats", "/proc/meminfo", "/proc/stat", "/proc/swaps", "/proc/uptime", "/proc/net/dev", } for _, valid := range validDestinations { path, err := filepath.Rel(filepath.Join(rootfs, valid), dest) if err != nil { return err } if path == "." { return nil } } for _, invalid := range invalidDestinations { path, err := filepath.Rel(filepath.Join(rootfs, invalid), dest) if err != nil { return err } if path == "." || !strings.HasPrefix(path, "..") { return fmt.Errorf("%q cannot be mounted because it is located inside %q", dest, invalid) } } return nil } func setupDevSymlinks(rootfs string) error { var links = [][2]string{ {"/proc/self/fd", "/dev/fd"}, {"/proc/self/fd/0", "/dev/stdin"}, {"/proc/self/fd/1", "/dev/stdout"}, {"/proc/self/fd/2", "/dev/stderr"}, } // kcore support can be toggled with CONFIG_PROC_KCORE; only create a symlink // in /dev if it exists in /proc. if _, err := os.Stat("/proc/kcore"); err == nil { links = append(links, [2]string{"/proc/kcore", "/dev/core"}) } for _, link := range links { var ( src = link[0] dst = filepath.Join(rootfs, link[1]) ) if err := os.Symlink(src, dst); err != nil && !os.IsExist(err) { return fmt.Errorf("symlink %s %s %s", src, dst, err) } } return nil } // If stdin, stdout, and/or stderr are pointing to `/dev/null` in the parent's rootfs // this method will make them point to `/dev/null` in this container's rootfs. This // needs to be called after we chroot/pivot into the container's rootfs so that any // symlinks are resolved locally. func reOpenDevNull() error { var stat, devNullStat unix.Stat_t file, err := os.OpenFile("/dev/null", os.O_RDWR, 0) if err != nil { return fmt.Errorf("Failed to open /dev/null - %s", err) } defer file.Close() if err := unix.Fstat(int(file.Fd()), &devNullStat); err != nil { return err } for fd := 0; fd < 3; fd++ { if err := unix.Fstat(fd, &stat); err != nil { return err } if stat.Rdev == devNullStat.Rdev { // Close and re-open the fd. if err := unix.Dup3(int(file.Fd()), fd, 0); err != nil { return err } } } return nil } // Create the device nodes in the container. func createDevices(config *configs.Config) error { useBindMount := system.RunningInUserNS() || config.Namespaces.Contains(configs.NEWUSER) oldMask := unix.Umask(0000) for _, node := range config.Devices { // containers running in a user namespace are not allowed to mknod // devices so we can just bind mount it from the host. if err := createDeviceNode(config.Rootfs, node, useBindMount); err != nil { unix.Umask(oldMask) return err } } unix.Umask(oldMask) return nil } func bindMountDeviceNode(dest string, node *configs.Device) error { f, err := os.Create(dest) if err != nil && !os.IsExist(err) { return err } if f != nil { f.Close() } return unix.Mount(node.Path, dest, "bind", unix.MS_BIND, "") } // Creates the device node in the rootfs of the container. func createDeviceNode(rootfs string, node *configs.Device, bind bool) error { dest := filepath.Join(rootfs, node.Path) if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { return err } if bind { return bindMountDeviceNode(dest, node) } if err := mknodDevice(dest, node); err != nil { if os.IsExist(err) { return nil } else if os.IsPermission(err) { return bindMountDeviceNode(dest, node) } return err } return nil } func mknodDevice(dest string, node *configs.Device) error { fileMode := node.FileMode switch node.Type { case 'c', 'u': fileMode |= unix.S_IFCHR case 'b': fileMode |= unix.S_IFBLK case 'p': fileMode |= unix.S_IFIFO default: return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path) } if err := unix.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil { return err } return unix.Chown(dest, int(node.Uid), int(node.Gid)) } func getMountInfo(mountinfo []*mount.Info, dir string) *mount.Info { for _, m := range mountinfo { if m.Mountpoint == dir { return m } } return nil } // Get the parent mount point of directory passed in as argument. Also return // optional fields. func getParentMount(rootfs string) (string, string, error) { var path string mountinfos, err := mount.GetMounts() if err != nil { return "", "", err } mountinfo := getMountInfo(mountinfos, rootfs) if mountinfo != nil { return rootfs, mountinfo.Optional, nil } path = rootfs for { path = filepath.Dir(path) mountinfo = getMountInfo(mountinfos, path) if mountinfo != nil { return path, mountinfo.Optional, nil } if path == "/" { break } } // If we are here, we did not find parent mount. Something is wrong. return "", "", fmt.Errorf("Could not find parent mount of %s", rootfs) } // Make parent mount private if it was shared func rootfsParentMountPrivate(rootfs string) error { sharedMount := false parentMount, optionalOpts, err := getParentMount(rootfs) if err != nil { return err } optsSplit := strings.Split(optionalOpts, " ") for _, opt := range optsSplit { if strings.HasPrefix(opt, "shared:") { sharedMount = true break } } // Make parent mount PRIVATE if it was shared. It is needed for two // reasons. First of all pivot_root() will fail if parent mount is // shared. Secondly when we bind mount rootfs it will propagate to // parent namespace and we don't want that to happen. if sharedMount { return unix.Mount("", parentMount, "", unix.MS_PRIVATE, "") } return nil } func prepareRoot(config *configs.Config) error { flag := unix.MS_SLAVE | unix.MS_REC if config.RootPropagation != 0 { flag = config.RootPropagation } if err := unix.Mount("", "/", "", uintptr(flag), ""); err != nil { return err } // Make parent mount private to make sure following bind mount does // not propagate in other namespaces. Also it will help with kernel // check pass in pivot_root. (IS_SHARED(new_mnt->mnt_parent)) if err := rootfsParentMountPrivate(config.Rootfs); err != nil { return err } return unix.Mount(config.Rootfs, config.Rootfs, "bind", unix.MS_BIND|unix.MS_REC, "") } func setReadonly() error { return unix.Mount("/", "/", "bind", unix.MS_BIND|unix.MS_REMOUNT|unix.MS_RDONLY|unix.MS_REC, "") } func setupPtmx(config *configs.Config) error { ptmx := filepath.Join(config.Rootfs, "dev/ptmx") if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { return err } if err := os.Symlink("pts/ptmx", ptmx); err != nil { return fmt.Errorf("symlink dev ptmx %s", err) } return nil } // pivotRoot will call pivot_root such that rootfs becomes the new root // filesystem, and everything else is cleaned up. func pivotRoot(rootfs string) error { // While the documentation may claim otherwise, pivot_root(".", ".") is // actually valid. What this results in is / being the new root but // /proc/self/cwd being the old root. Since we can play around with the cwd // with pivot_root this allows us to pivot without creating directories in // the rootfs. Shout-outs to the LXC developers for giving us this idea. oldroot, err := unix.Open("/", unix.O_DIRECTORY|unix.O_RDONLY, 0) if err != nil { return err } defer unix.Close(oldroot) newroot, err := unix.Open(rootfs, unix.O_DIRECTORY|unix.O_RDONLY, 0) if err != nil { return err } defer unix.Close(newroot) // Change to the new root so that the pivot_root actually acts on it. if err := unix.Fchdir(newroot); err != nil { return err } if err := unix.PivotRoot(".", "."); err != nil { return fmt.Errorf("pivot_root %s", err) } // Currently our "." is oldroot (according to the current kernel code). // However, purely for safety, we will fchdir(oldroot) since there isn't // really any guarantee from the kernel what /proc/self/cwd will be after a // pivot_root(2). if err := unix.Fchdir(oldroot); err != nil { return err } // Make oldroot rprivate to make sure our unmounts don't propagate to the // host (and thus bork the machine). if err := unix.Mount("", ".", "", unix.MS_PRIVATE|unix.MS_REC, ""); err != nil { return err } // Preform the unmount. MNT_DETACH allows us to unmount /proc/self/cwd. if err := unix.Unmount(".", unix.MNT_DETACH); err != nil { return err } // Switch back to our shiny new root. if err := unix.Chdir("/"); err != nil { return fmt.Errorf("chdir / %s", err) } return nil } func msMoveRoot(rootfs string) error { if err := unix.Mount(rootfs, "/", "", unix.MS_MOVE, ""); err != nil { return err } if err := unix.Chroot("."); err != nil { return err } return unix.Chdir("/") } // createIfNotExists creates a file or a directory only if it does not already exist. func createIfNotExists(path string, isDir bool) error { if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { if isDir { return os.MkdirAll(path, 0755) } if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return err } f, err := os.OpenFile(path, os.O_CREATE, 0755) if err != nil { return err } f.Close() } } return nil } // readonlyPath will make a path read only. func readonlyPath(path string) error { if err := unix.Mount(path, path, "", unix.MS_BIND|unix.MS_REC, ""); err != nil { if os.IsNotExist(err) { return nil } return err } return unix.Mount(path, path, "", unix.MS_BIND|unix.MS_REMOUNT|unix.MS_RDONLY|unix.MS_REC, "") } // remountReadonly will remount an existing mount point and ensure that it is read-only. func remountReadonly(m *configs.Mount) error { var ( dest = m.Destination flags = m.Flags ) for i := 0; i < 5; i++ { if err := unix.Mount("", dest, "", uintptr(flags|unix.MS_REMOUNT|unix.MS_RDONLY), ""); err != nil { switch err { case unix.EBUSY: time.Sleep(100 * time.Millisecond) continue default: return err } } return nil } return fmt.Errorf("unable to mount %s as readonly max retries reached", dest) } // maskPath masks the top of the specified path inside a container to avoid // security issues from processes reading information from non-namespace aware // mounts ( proc/kcore ). // For files, maskPath bind mounts /dev/null over the top of the specified path. // For directories, maskPath mounts read-only tmpfs over the top of the specified path. func maskPath(path string) error { if err := unix.Mount("/dev/null", path, "", unix.MS_BIND, ""); err != nil && !os.IsNotExist(err) { if err == unix.ENOTDIR { return unix.Mount("tmpfs", path, "tmpfs", unix.MS_RDONLY, "") } return err } return nil } // writeSystemProperty writes the value to a path under /proc/sys as determined from the key. // For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward. func writeSystemProperty(key, value string) error { keyPath := strings.Replace(key, ".", "/", -1) return ioutil.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0644) } func remount(m *configs.Mount, rootfs string) error { var ( dest = m.Destination ) if !strings.HasPrefix(dest, rootfs) { dest = filepath.Join(rootfs, dest) } if err := unix.Mount(m.Source, dest, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), ""); err != nil { return err } return nil } // Do the mount operation followed by additional mounts required to take care // of propagation flags. func mountPropagate(m *configs.Mount, rootfs string, mountLabel string) error { var ( dest = m.Destination data = label.FormatMountLabel(m.Data, mountLabel) flags = m.Flags ) if libcontainerUtils.CleanPath(dest) == "/dev" { flags &= ^unix.MS_RDONLY } copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP if !(copyUp || strings.HasPrefix(dest, rootfs)) { dest = filepath.Join(rootfs, dest) } if err := unix.Mount(m.Source, dest, m.Device, uintptr(flags), data); err != nil { return err } for _, pflag := range m.PropagationFlags { if err := unix.Mount("", dest, "", uintptr(pflag), ""); err != nil { return err } } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/seccomp/000077500000000000000000000000001315410276000270465ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/seccomp/config.go000066400000000000000000000047371315410276000306550ustar00rootroot00000000000000package seccomp import ( "fmt" "github.com/opencontainers/runc/libcontainer/configs" ) var operators = map[string]configs.Operator{ "SCMP_CMP_NE": configs.NotEqualTo, "SCMP_CMP_LT": configs.LessThan, "SCMP_CMP_LE": configs.LessThanOrEqualTo, "SCMP_CMP_EQ": configs.EqualTo, "SCMP_CMP_GE": configs.GreaterThanOrEqualTo, "SCMP_CMP_GT": configs.GreaterThan, "SCMP_CMP_MASKED_EQ": configs.MaskEqualTo, } var actions = map[string]configs.Action{ "SCMP_ACT_KILL": configs.Kill, "SCMP_ACT_ERRNO": configs.Errno, "SCMP_ACT_TRAP": configs.Trap, "SCMP_ACT_ALLOW": configs.Allow, "SCMP_ACT_TRACE": configs.Trace, } var archs = map[string]string{ "SCMP_ARCH_X86": "x86", "SCMP_ARCH_X86_64": "amd64", "SCMP_ARCH_X32": "x32", "SCMP_ARCH_ARM": "arm", "SCMP_ARCH_AARCH64": "arm64", "SCMP_ARCH_MIPS": "mips", "SCMP_ARCH_MIPS64": "mips64", "SCMP_ARCH_MIPS64N32": "mips64n32", "SCMP_ARCH_MIPSEL": "mipsel", "SCMP_ARCH_MIPSEL64": "mipsel64", "SCMP_ARCH_MIPSEL64N32": "mipsel64n32", "SCMP_ARCH_PPC": "ppc", "SCMP_ARCH_PPC64": "ppc64", "SCMP_ARCH_PPC64LE": "ppc64le", "SCMP_ARCH_S390": "s390", "SCMP_ARCH_S390X": "s390x", } // ConvertStringToOperator converts a string into a Seccomp comparison operator. // Comparison operators use the names they are assigned by Libseccomp's header. // Attempting to convert a string that is not a valid operator results in an // error. func ConvertStringToOperator(in string) (configs.Operator, error) { if op, ok := operators[in]; ok == true { return op, nil } return 0, fmt.Errorf("string %s is not a valid operator for seccomp", in) } // ConvertStringToAction converts a string into a Seccomp rule match action. // Actions use the names they are assigned in Libseccomp's header, though some // (notable, SCMP_ACT_TRACE) are not available in this implementation and will // return errors. // Attempting to convert a string that is not a valid action results in an // error. func ConvertStringToAction(in string) (configs.Action, error) { if act, ok := actions[in]; ok == true { return act, nil } return 0, fmt.Errorf("string %s is not a valid action for seccomp", in) } // ConvertStringToArch converts a string into a Seccomp comparison arch. func ConvertStringToArch(in string) (string, error) { if arch, ok := archs[in]; ok == true { return arch, nil } return "", fmt.Errorf("string %s is not a valid arch for seccomp", in) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_linux.go000066400000000000000000000131271315410276000322510ustar00rootroot00000000000000// +build linux,cgo,seccomp package seccomp import ( "bufio" "fmt" "os" "strings" "github.com/opencontainers/runc/libcontainer/configs" libseccomp "github.com/seccomp/libseccomp-golang" "golang.org/x/sys/unix" ) var ( actAllow = libseccomp.ActAllow actTrap = libseccomp.ActTrap actKill = libseccomp.ActKill actTrace = libseccomp.ActTrace.SetReturnCode(int16(unix.EPERM)) actErrno = libseccomp.ActErrno.SetReturnCode(int16(unix.EPERM)) ) // Filters given syscalls in a container, preventing them from being used // Started in the container init process, and carried over to all child processes // Setns calls, however, require a separate invocation, as they are not children // of the init until they join the namespace func InitSeccomp(config *configs.Seccomp) error { if config == nil { return fmt.Errorf("cannot initialize Seccomp - nil config passed") } defaultAction, err := getAction(config.DefaultAction) if err != nil { return fmt.Errorf("error initializing seccomp - invalid default action") } filter, err := libseccomp.NewFilter(defaultAction) if err != nil { return fmt.Errorf("error creating filter: %s", err) } // Add extra architectures for _, arch := range config.Architectures { scmpArch, err := libseccomp.GetArchFromString(arch) if err != nil { return err } if err := filter.AddArch(scmpArch); err != nil { return err } } // Unset no new privs bit if err := filter.SetNoNewPrivsBit(false); err != nil { return fmt.Errorf("error setting no new privileges: %s", err) } // Add a rule for each syscall for _, call := range config.Syscalls { if call == nil { return fmt.Errorf("encountered nil syscall while initializing Seccomp") } if err = matchCall(filter, call); err != nil { return err } } if err = filter.Load(); err != nil { return fmt.Errorf("error loading seccomp filter into kernel: %s", err) } return nil } // IsEnabled returns if the kernel has been configured to support seccomp. func IsEnabled() bool { // Try to read from /proc/self/status for kernels > 3.8 s, err := parseStatusFile("/proc/self/status") if err != nil { // Check if Seccomp is supported, via CONFIG_SECCOMP. if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL { // Make sure the kernel has CONFIG_SECCOMP_FILTER. if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL { return true } } return false } _, ok := s["Seccomp"] return ok } // Convert Libcontainer Action to Libseccomp ScmpAction func getAction(act configs.Action) (libseccomp.ScmpAction, error) { switch act { case configs.Kill: return actKill, nil case configs.Errno: return actErrno, nil case configs.Trap: return actTrap, nil case configs.Allow: return actAllow, nil case configs.Trace: return actTrace, nil default: return libseccomp.ActInvalid, fmt.Errorf("invalid action, cannot use in rule") } } // Convert Libcontainer Operator to Libseccomp ScmpCompareOp func getOperator(op configs.Operator) (libseccomp.ScmpCompareOp, error) { switch op { case configs.EqualTo: return libseccomp.CompareEqual, nil case configs.NotEqualTo: return libseccomp.CompareNotEqual, nil case configs.GreaterThan: return libseccomp.CompareGreater, nil case configs.GreaterThanOrEqualTo: return libseccomp.CompareGreaterEqual, nil case configs.LessThan: return libseccomp.CompareLess, nil case configs.LessThanOrEqualTo: return libseccomp.CompareLessOrEqual, nil case configs.MaskEqualTo: return libseccomp.CompareMaskedEqual, nil default: return libseccomp.CompareInvalid, fmt.Errorf("invalid operator, cannot use in rule") } } // Convert Libcontainer Arg to Libseccomp ScmpCondition func getCondition(arg *configs.Arg) (libseccomp.ScmpCondition, error) { cond := libseccomp.ScmpCondition{} if arg == nil { return cond, fmt.Errorf("cannot convert nil to syscall condition") } op, err := getOperator(arg.Op) if err != nil { return cond, err } return libseccomp.MakeCondition(arg.Index, op, arg.Value, arg.ValueTwo) } // Add a rule to match a single syscall func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall) error { if call == nil || filter == nil { return fmt.Errorf("cannot use nil as syscall to block") } if len(call.Name) == 0 { return fmt.Errorf("empty string is not a valid syscall") } // If we can't resolve the syscall, assume it's not supported on this kernel // Ignore it, don't error out callNum, err := libseccomp.GetSyscallFromName(call.Name) if err != nil { return nil } // Convert the call's action to the libseccomp equivalent callAct, err := getAction(call.Action) if err != nil { return err } // Unconditional match - just add the rule if len(call.Args) == 0 { if err = filter.AddRule(callNum, callAct); err != nil { return err } } else { // Conditional match - convert the per-arg rules into library format conditions := []libseccomp.ScmpCondition{} for _, cond := range call.Args { newCond, err := getCondition(cond) if err != nil { return err } conditions = append(conditions, newCond) } if err = filter.AddRuleConditional(callNum, callAct, conditions); err != nil { return err } } return nil } func parseStatusFile(path string) (map[string]string, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() s := bufio.NewScanner(f) status := make(map[string]string) for s.Scan() { text := s.Text() parts := strings.Split(text, ":") if len(parts) <= 1 { continue } status[parts[0]] = parts[1] } if err := s.Err(); err != nil { return nil, err } return status, nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_unsupported.go000066400000000000000000000007611315410276000335020ustar00rootroot00000000000000// +build !linux !cgo !seccomp package seccomp import ( "errors" "github.com/opencontainers/runc/libcontainer/configs" ) var ErrSeccompNotEnabled = errors.New("seccomp: config provided but seccomp not supported") // InitSeccomp does nothing because seccomp is not supported. func InitSeccomp(config *configs.Seccomp) error { if config != nil { return ErrSeccompNotEnabled } return nil } // IsEnabled returns false, because it is not supported. func IsEnabled() bool { return false } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/setgroups_linux.go000066400000000000000000000004451315410276000312210ustar00rootroot00000000000000// +build linux,go1.5 package libcontainer import "syscall" // Set the GidMappingsEnableSetgroups member to true, so the process's // setgroups proc entry wont be set to 'deny' if GidMappings are set func enableSetgroups(sys *syscall.SysProcAttr) { sys.GidMappingsEnableSetgroups = true } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/setns_init_linux.go000066400000000000000000000032161315410276000313440ustar00rootroot00000000000000// +build linux package libcontainer import ( "fmt" "os" "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/keys" "github.com/opencontainers/runc/libcontainer/seccomp" "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/selinux/go-selinux/label" "golang.org/x/sys/unix" ) // linuxSetnsInit performs the container's initialization for running a new process // inside an existing container. type linuxSetnsInit struct { pipe *os.File consoleSocket *os.File config *initConfig } func (l *linuxSetnsInit) getSessionRingName() string { return fmt.Sprintf("_ses.%s", l.config.ContainerId) } func (l *linuxSetnsInit) Init() error { if !l.config.Config.NoNewKeyring { // do not inherit the parent's session keyring if _, err := keys.JoinSessionKeyring(l.getSessionRingName()); err != nil { return err } } if l.config.CreateConsole { if err := setupConsole(l.consoleSocket, l.config, false); err != nil { return err } if err := system.Setctty(); err != nil { return err } } if l.config.NoNewPrivileges { if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil { return err } } if l.config.Config.Seccomp != nil { if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil { return err } } if err := finalizeNamespace(l.config); err != nil { return err } if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil { return err } if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil { return err } return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/stacktrace/000077500000000000000000000000001315410276000275415ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/stacktrace/capture.go000066400000000000000000000011441315410276000315330ustar00rootroot00000000000000package stacktrace import "runtime" // Capture captures a stacktrace for the current calling go program // // skip is the number of frames to skip func Capture(userSkip int) Stacktrace { var ( skip = userSkip + 1 // add one for our own function frames []Frame prevPc uintptr ) for i := skip; ; i++ { pc, file, line, ok := runtime.Caller(i) //detect if caller is repeated to avoid loop, gccgo //currently runs into a loop without this check if !ok || pc == prevPc { break } frames = append(frames, NewFrame(pc, file, line)) prevPc = pc } return Stacktrace{ Frames: frames, } } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/stacktrace/frame.go000066400000000000000000000013341315410276000311630ustar00rootroot00000000000000package stacktrace import ( "path/filepath" "runtime" "strings" ) // NewFrame returns a new stack frame for the provided information func NewFrame(pc uintptr, file string, line int) Frame { fn := runtime.FuncForPC(pc) if fn == nil { return Frame{} } pack, name := parseFunctionName(fn.Name()) return Frame{ Line: line, File: filepath.Base(file), Package: pack, Function: name, } } func parseFunctionName(name string) (string, string) { i := strings.LastIndex(name, ".") if i == -1 { return "", name } return name[:i], name[i+1:] } // Frame contains all the information for a stack frame within a go program type Frame struct { File string Function string Package string Line int } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/stacktrace/stacktrace.go000066400000000000000000000000771315410276000322200ustar00rootroot00000000000000package stacktrace type Stacktrace struct { Frames []Frame } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/standard_init_linux.go000066400000000000000000000133651315410276000320160ustar00rootroot00000000000000// +build linux package libcontainer import ( "fmt" "os" "os/exec" "syscall" //only for Exec "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/keys" "github.com/opencontainers/runc/libcontainer/seccomp" "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/selinux/go-selinux/label" "golang.org/x/sys/unix" ) type linuxStandardInit struct { pipe *os.File consoleSocket *os.File parentPid int fifoFd int config *initConfig } func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) { var newperms uint32 if l.config.Config.Namespaces.Contains(configs.NEWUSER) { // with user ns we need 'other' search permissions newperms = 0x8 } else { // without user ns we need 'UID' search permissions newperms = 0x80000 } // create a unique per session container name that we can // join in setns; however, other containers can also join it return fmt.Sprintf("_ses.%s", l.config.ContainerId), 0xffffffff, newperms } func (l *linuxStandardInit) Init() error { if !l.config.Config.NoNewKeyring { ringname, keepperms, newperms := l.getSessionRingParams() // do not inherit the parent's session keyring sessKeyId, err := keys.JoinSessionKeyring(ringname) if err != nil { return err } // make session keyring searcheable if err := keys.ModKeyringPerm(sessKeyId, keepperms, newperms); err != nil { return err } } if err := setupNetwork(l.config); err != nil { return err } if err := setupRoute(l.config.Config); err != nil { return err } label.Init() // prepareRootfs() can be executed only for a new mount namespace. if l.config.Config.Namespaces.Contains(configs.NEWNS) { if err := prepareRootfs(l.pipe, l.config.Config); err != nil { return err } } // Set up the console. This has to be done *before* we finalize the rootfs, // but *after* we've given the user the chance to set up all of the mounts // they wanted. if l.config.CreateConsole { if err := setupConsole(l.consoleSocket, l.config, true); err != nil { return err } if err := system.Setctty(); err != nil { return err } } // Finish the rootfs setup. if l.config.Config.Namespaces.Contains(configs.NEWNS) { if err := finalizeRootfs(l.config.Config); err != nil { return err } } if hostname := l.config.Config.Hostname; hostname != "" { if err := unix.Sethostname([]byte(hostname)); err != nil { return err } } if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil { return err } if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil { return err } for key, value := range l.config.Config.Sysctl { if err := writeSystemProperty(key, value); err != nil { return err } } for _, path := range l.config.Config.ReadonlyPaths { if err := readonlyPath(path); err != nil { return err } } for _, path := range l.config.Config.MaskPaths { if err := maskPath(path); err != nil { return err } } pdeath, err := system.GetParentDeathSignal() if err != nil { return err } if l.config.NoNewPrivileges { if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil { return err } } // Tell our parent that we're ready to Execv. This must be done before the // Seccomp rules have been applied, because we need to be able to read and // write to a socket. if err := syncParentReady(l.pipe); err != nil { return err } // Without NoNewPrivileges seccomp is a privileged operation, so we need to // do this before dropping capabilities; otherwise do it as late as possible // just before execve so as few syscalls take place after it as possible. if l.config.Config.Seccomp != nil && !l.config.NoNewPrivileges { if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil { return err } } if err := finalizeNamespace(l.config); err != nil { return err } // finalizeNamespace can change user/group which clears the parent death // signal, so we restore it here. if err := pdeath.Restore(); err != nil { return err } // compare the parent from the initial start of the init process and make sure that it did not change. // if the parent changes that means it died and we were reparented to something else so we should // just kill ourself and not cause problems for someone else. if unix.Getppid() != l.parentPid { return unix.Kill(unix.Getpid(), unix.SIGKILL) } // check for the arg before waiting to make sure it exists and it is returned // as a create time error. name, err := exec.LookPath(l.config.Args[0]) if err != nil { return err } // close the pipe to signal that we have completed our init. l.pipe.Close() // Wait for the FIFO to be opened on the other side before exec-ing the // user process. We open it through /proc/self/fd/$fd, because the fd that // was given to us was an O_PATH fd to the fifo itself. Linux allows us to // re-open an O_PATH fd through /proc. fd, err := unix.Open(fmt.Sprintf("/proc/self/fd/%d", l.fifoFd), unix.O_WRONLY|unix.O_CLOEXEC, 0) if err != nil { return newSystemErrorWithCause(err, "openat exec fifo") } if _, err := unix.Write(fd, []byte("0")); err != nil { return newSystemErrorWithCause(err, "write 0 exec fifo") } if l.config.Config.Seccomp != nil && l.config.NoNewPrivileges { if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil { return newSystemErrorWithCause(err, "init seccomp") } } // close the statedir fd before exec because the kernel resets dumpable in the wrong order // https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1290-L1318 unix.Close(l.fifoFd) if err := syscall.Exec(name, l.config.Args[0:], os.Environ()); err != nil { return newSystemErrorWithCause(err, "exec user process") } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/state_linux.go000066400000000000000000000122501315410276000303030ustar00rootroot00000000000000// +build linux package libcontainer import ( "fmt" "os" "path/filepath" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/utils" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) func newStateTransitionError(from, to containerState) error { return &stateTransitionError{ From: from.status().String(), To: to.status().String(), } } // stateTransitionError is returned when an invalid state transition happens from one // state to another. type stateTransitionError struct { From string To string } func (s *stateTransitionError) Error() string { return fmt.Sprintf("invalid state transition from %s to %s", s.From, s.To) } type containerState interface { transition(containerState) error destroy() error status() Status } func destroy(c *linuxContainer) error { if !c.config.Namespaces.Contains(configs.NEWPID) { if err := signalAllProcesses(c.cgroupManager, unix.SIGKILL); err != nil { logrus.Warn(err) } } err := c.cgroupManager.Destroy() if rerr := os.RemoveAll(c.root); err == nil { err = rerr } c.initProcess = nil if herr := runPoststopHooks(c); err == nil { err = herr } c.state = &stoppedState{c: c} return err } func runPoststopHooks(c *linuxContainer) error { if c.config.Hooks != nil { s := configs.HookState{ Version: c.config.Version, ID: c.id, Bundle: utils.SearchLabels(c.config.Labels, "bundle"), } for _, hook := range c.config.Hooks.Poststop { if err := hook.Run(s); err != nil { return err } } } return nil } // stoppedState represents a container is a stopped/destroyed state. type stoppedState struct { c *linuxContainer } func (b *stoppedState) status() Status { return Stopped } func (b *stoppedState) transition(s containerState) error { switch s.(type) { case *runningState, *restoredState: b.c.state = s return nil case *stoppedState: return nil } return newStateTransitionError(b, s) } func (b *stoppedState) destroy() error { return destroy(b.c) } // runningState represents a container that is currently running. type runningState struct { c *linuxContainer } func (r *runningState) status() Status { return Running } func (r *runningState) transition(s containerState) error { switch s.(type) { case *stoppedState: t, err := r.c.runType() if err != nil { return err } if t == Running { return newGenericError(fmt.Errorf("container still running"), ContainerNotStopped) } r.c.state = s return nil case *pausedState: r.c.state = s return nil case *runningState: return nil } return newStateTransitionError(r, s) } func (r *runningState) destroy() error { t, err := r.c.runType() if err != nil { return err } if t == Running { return newGenericError(fmt.Errorf("container is not destroyed"), ContainerNotStopped) } return destroy(r.c) } type createdState struct { c *linuxContainer } func (i *createdState) status() Status { return Created } func (i *createdState) transition(s containerState) error { switch s.(type) { case *runningState, *pausedState, *stoppedState: i.c.state = s return nil case *createdState: return nil } return newStateTransitionError(i, s) } func (i *createdState) destroy() error { i.c.initProcess.signal(unix.SIGKILL) return destroy(i.c) } // pausedState represents a container that is currently pause. It cannot be destroyed in a // paused state and must transition back to running first. type pausedState struct { c *linuxContainer } func (p *pausedState) status() Status { return Paused } func (p *pausedState) transition(s containerState) error { switch s.(type) { case *runningState, *stoppedState: p.c.state = s return nil case *pausedState: return nil } return newStateTransitionError(p, s) } func (p *pausedState) destroy() error { t, err := p.c.runType() if err != nil { return err } if t != Running && t != Created { if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil { return err } return destroy(p.c) } return newGenericError(fmt.Errorf("container is paused"), ContainerPaused) } // restoredState is the same as the running state but also has associated checkpoint // information that maybe need destroyed when the container is stopped and destroy is called. type restoredState struct { imageDir string c *linuxContainer } func (r *restoredState) status() Status { return Running } func (r *restoredState) transition(s containerState) error { switch s.(type) { case *stoppedState, *runningState: return nil } return newStateTransitionError(r, s) } func (r *restoredState) destroy() error { if _, err := os.Stat(filepath.Join(r.c.root, "checkpoint")); err != nil { if !os.IsNotExist(err) { return err } } return destroy(r.c) } // loadedState is used whenever a container is restored, loaded, or setting additional // processes inside and it should not be destroyed when it is exiting. type loadedState struct { c *linuxContainer s Status } func (n *loadedState) status() Status { return n.s } func (n *loadedState) transition(s containerState) error { n.c.state = s return nil } func (n *loadedState) destroy() error { if err := n.c.refreshState(); err != nil { return err } return n.c.state.destroy() } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/stats.go000066400000000000000000000004041315410276000271000ustar00rootroot00000000000000package libcontainer type NetworkInterface struct { // Name is the name of the network interface. Name string RxBytes uint64 RxPackets uint64 RxErrors uint64 RxDropped uint64 TxBytes uint64 TxPackets uint64 TxErrors uint64 TxDropped uint64 } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/stats_freebsd.go000066400000000000000000000001141315410276000305700ustar00rootroot00000000000000package libcontainer type Stats struct { Interfaces []*NetworkInterface } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/stats_linux.go000066400000000000000000000002471315410276000303240ustar00rootroot00000000000000package libcontainer import "github.com/opencontainers/runc/libcontainer/cgroups" type Stats struct { Interfaces []*NetworkInterface CgroupStats *cgroups.Stats } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/stats_solaris.go000066400000000000000000000001371315410276000306370ustar00rootroot00000000000000package libcontainer // Solaris - TODO type Stats struct { Interfaces []*NetworkInterface } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/stats_windows.go000066400000000000000000000001141315410276000306500ustar00rootroot00000000000000package libcontainer type Stats struct { Interfaces []*NetworkInterface } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/sync.go000066400000000000000000000051651315410276000267270ustar00rootroot00000000000000package libcontainer import ( "encoding/json" "fmt" "io" "github.com/opencontainers/runc/libcontainer/utils" ) type syncType string // Constants that are used for synchronisation between the parent and child // during container setup. They come in pairs (with procError being a generic // response which is followed by a &genericError). // // [ child ] <-> [ parent ] // // procHooks --> [run hooks] // <-- procResume // // procConsole --> // <-- procConsoleReq // [send(fd)] --> [recv(fd)] // <-- procConsoleAck // // procReady --> [final setup] // <-- procRun const ( procError syncType = "procError" procReady syncType = "procReady" procRun syncType = "procRun" procHooks syncType = "procHooks" procResume syncType = "procResume" ) type syncT struct { Type syncType `json:"type"` } // writeSync is used to write to a synchronisation pipe. An error is returned // if there was a problem writing the payload. func writeSync(pipe io.Writer, sync syncType) error { if err := utils.WriteJSON(pipe, syncT{sync}); err != nil { return err } return nil } // readSync is used to read from a synchronisation pipe. An error is returned // if we got a genericError, the pipe was closed, or we got an unexpected flag. func readSync(pipe io.Reader, expected syncType) error { var procSync syncT if err := json.NewDecoder(pipe).Decode(&procSync); err != nil { if err == io.EOF { return fmt.Errorf("parent closed synchronisation channel") } if procSync.Type == procError { var ierr genericError if err := json.NewDecoder(pipe).Decode(&ierr); err != nil { return fmt.Errorf("failed reading error from parent: %v", err) } return &ierr } if procSync.Type != expected { return fmt.Errorf("invalid synchronisation flag from parent") } } return nil } // parseSync runs the given callback function on each syncT received from the // child. It will return once io.EOF is returned from the given pipe. func parseSync(pipe io.Reader, fn func(*syncT) error) error { dec := json.NewDecoder(pipe) for { var sync syncT if err := dec.Decode(&sync); err != nil { if err == io.EOF { break } return err } // We handle this case outside fn for cleanliness reasons. var ierr *genericError if sync.Type == procError { if err := dec.Decode(&ierr); err != nil && err != io.EOF { return newSystemErrorWithCause(err, "decoding proc error from init") } if ierr != nil { return ierr } // Programmer error. panic("No error following JSON procError payload.") } if err := fn(&sync); err != nil { return err } } return nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/000077500000000000000000000000001315410276000267415ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/linux.go000066400000000000000000000062401315410276000304310ustar00rootroot00000000000000// +build linux package system import ( "bufio" "fmt" "os" "os/exec" "syscall" // only for exec "unsafe" "golang.org/x/sys/unix" ) // If arg2 is nonzero, set the "child subreaper" attribute of the // calling process; if arg2 is zero, unset the attribute. When a // process is marked as a child subreaper, all of the children // that it creates, and their descendants, will be marked as // having a subreaper. In effect, a subreaper fulfills the role // of init(1) for its descendant processes. Upon termination of // a process that is orphaned (i.e., its immediate parent has // already terminated) and marked as having a subreaper, the // nearest still living ancestor subreaper will receive a SIGCHLD // signal and be able to wait(2) on the process to discover its // termination status. const PR_SET_CHILD_SUBREAPER = 36 type ParentDeathSignal int func (p ParentDeathSignal) Restore() error { if p == 0 { return nil } current, err := GetParentDeathSignal() if err != nil { return err } if p == current { return nil } return p.Set() } func (p ParentDeathSignal) Set() error { return SetParentDeathSignal(uintptr(p)) } func Execv(cmd string, args []string, env []string) error { name, err := exec.LookPath(cmd) if err != nil { return err } return syscall.Exec(name, args, env) } func Prlimit(pid, resource int, limit unix.Rlimit) error { _, _, err := unix.RawSyscall6(unix.SYS_PRLIMIT64, uintptr(pid), uintptr(resource), uintptr(unsafe.Pointer(&limit)), uintptr(unsafe.Pointer(&limit)), 0, 0) if err != 0 { return err } return nil } func SetParentDeathSignal(sig uintptr) error { if err := unix.Prctl(unix.PR_SET_PDEATHSIG, sig, 0, 0, 0); err != nil { return err } return nil } func GetParentDeathSignal() (ParentDeathSignal, error) { var sig int if err := unix.Prctl(unix.PR_GET_PDEATHSIG, uintptr(unsafe.Pointer(&sig)), 0, 0, 0); err != nil { return -1, err } return ParentDeathSignal(sig), nil } func SetKeepCaps() error { if err := unix.Prctl(unix.PR_SET_KEEPCAPS, 1, 0, 0, 0); err != nil { return err } return nil } func ClearKeepCaps() error { if err := unix.Prctl(unix.PR_SET_KEEPCAPS, 0, 0, 0, 0); err != nil { return err } return nil } func Setctty() error { if err := unix.IoctlSetInt(0, unix.TIOCSCTTY, 0); err != nil { return err } return nil } // RunningInUserNS detects whether we are currently running in a user namespace. // Copied from github.com/lxc/lxd/shared/util.go func RunningInUserNS() bool { file, err := os.Open("/proc/self/uid_map") if err != nil { // This kernel-provided file only exists if user namespaces are supported return false } defer file.Close() buf := bufio.NewReader(file) l, _, err := buf.ReadLine() if err != nil { return false } line := string(l) var a, b, c int64 fmt.Sscanf(line, "%d %d %d", &a, &b, &c) /* * We assume we are in the initial user namespace if we have a full * range - 4294967295 uids starting at uid 0. */ if a == 0 && b == 0 && c == 4294967295 { return false } return true } // SetSubreaper sets the value i as the subreaper setting for the calling process func SetSubreaper(i int) error { return unix.Prctl(PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/proc.go000066400000000000000000000054661315410276000302460ustar00rootroot00000000000000package system import ( "fmt" "io/ioutil" "path/filepath" "strconv" "strings" ) // State is the status of a process. type State rune const ( // Only values for Linux 3.14 and later are listed here Dead State = 'X' DiskSleep State = 'D' Running State = 'R' Sleeping State = 'S' Stopped State = 'T' TracingStop State = 't' Zombie State = 'Z' ) // String forms of the state from proc(5)'s documentation for // /proc/[pid]/status' "State" field. func (s State) String() string { switch s { case Dead: return "dead" case DiskSleep: return "disk sleep" case Running: return "running" case Sleeping: return "sleeping" case Stopped: return "stopped" case TracingStop: return "tracing stop" case Zombie: return "zombie" default: return fmt.Sprintf("unknown (%c)", s) } } // Stat_t represents the information from /proc/[pid]/stat, as // described in proc(5) with names based on the /proc/[pid]/status // fields. type Stat_t struct { // PID is the process ID. PID uint // Name is the command run by the process. Name string // State is the state of the process. State State // StartTime is the number of clock ticks after system boot (since // Linux 2.6). StartTime uint64 } // Stat returns a Stat_t instance for the specified process. func Stat(pid int) (stat Stat_t, err error) { bytes, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat")) if err != nil { return stat, err } return parseStat(string(bytes)) } // GetProcessStartTime is deprecated. Use Stat(pid) and // Stat_t.StartTime instead. func GetProcessStartTime(pid int) (string, error) { stat, err := Stat(pid) if err != nil { return "", err } return fmt.Sprintf("%d", stat.StartTime), nil } func parseStat(data string) (stat Stat_t, err error) { // From proc(5), field 2 could contain space and is inside `(` and `)`. // The following is an example: // 89653 (gunicorn: maste) S 89630 89653 89653 0 -1 4194560 29689 28896 0 3 146 32 76 19 20 0 1 0 2971844 52965376 3920 18446744073709551615 1 1 0 0 0 0 0 16781312 137447943 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0 i := strings.LastIndex(data, ")") if i <= 2 || i >= len(data)-1 { return stat, fmt.Errorf("invalid stat data: %q", data) } parts := strings.SplitN(data[:i], "(", 2) if len(parts) != 2 { return stat, fmt.Errorf("invalid stat data: %q", data) } stat.Name = parts[1] _, err = fmt.Sscanf(parts[0], "%d", &stat.PID) if err != nil { return stat, err } // parts indexes should be offset by 3 from the field number given // proc(5), because parts is zero-indexed and we've removed fields // one (PID) and two (Name) in the paren-split. parts = strings.Split(data[i+2:], " ") var state int fmt.Sscanf(parts[3-3], "%c", &state) stat.State = State(state) fmt.Sscanf(parts[22-3], "%d", &stat.StartTime) return stat, nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/syscall_linux_386.go000066400000000000000000000007531315410276000325660ustar00rootroot00000000000000// +build linux,386 package system import ( "golang.org/x/sys/unix" ) // Setuid sets the uid of the calling thread to the specified uid. func Setuid(uid int) (err error) { _, _, e1 := unix.RawSyscall(unix.SYS_SETUID32, uintptr(uid), 0, 0) if e1 != 0 { err = e1 } return } // Setgid sets the gid of the calling thread to the specified gid. func Setgid(gid int) (err error) { _, _, e1 := unix.RawSyscall(unix.SYS_SETGID32, uintptr(gid), 0, 0) if e1 != 0 { err = e1 } return } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/syscall_linux_64.go000066400000000000000000000010451315410276000324720ustar00rootroot00000000000000// +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x package system import ( "golang.org/x/sys/unix" ) // Setuid sets the uid of the calling thread to the specified uid. func Setuid(uid int) (err error) { _, _, e1 := unix.RawSyscall(unix.SYS_SETUID, uintptr(uid), 0, 0) if e1 != 0 { err = e1 } return } // Setgid sets the gid of the calling thread to the specified gid. func Setgid(gid int) (err error) { _, _, e1 := unix.RawSyscall(unix.SYS_SETGID, uintptr(gid), 0, 0) if e1 != 0 { err = e1 } return } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/syscall_linux_arm.go000066400000000000000000000007531315410276000330250ustar00rootroot00000000000000// +build linux,arm package system import ( "golang.org/x/sys/unix" ) // Setuid sets the uid of the calling thread to the specified uid. func Setuid(uid int) (err error) { _, _, e1 := unix.RawSyscall(unix.SYS_SETUID32, uintptr(uid), 0, 0) if e1 != 0 { err = e1 } return } // Setgid sets the gid of the calling thread to the specified gid. func Setgid(gid int) (err error) { _, _, e1 := unix.RawSyscall(unix.SYS_SETGID32, uintptr(gid), 0, 0) if e1 != 0 { err = e1 } return } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/sysconfig.go000066400000000000000000000002321315410276000312710ustar00rootroot00000000000000// +build cgo,linux cgo,freebsd package system /* #include */ import "C" func GetClockTicks() int { return int(C.sysconf(C._SC_CLK_TCK)) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/sysconfig_notcgo.go000066400000000000000000000007251315410276000326510ustar00rootroot00000000000000// +build !cgo windows package system func GetClockTicks() int { // TODO figure out a better alternative for platforms where we're missing cgo // // TODO Windows. This could be implemented using Win32 QueryPerformanceFrequency(). // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644905(v=vs.85).aspx // // An example of its usage can be found here. // https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx return 100 } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/unsupported.go000066400000000000000000000002331315410276000316560ustar00rootroot00000000000000// +build !linux package system // RunningInUserNS is a stub for non-Linux systems // Always returns false func RunningInUserNS() bool { return false } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/system/xattrs_linux.go000066400000000000000000000015721315410276000320410ustar00rootroot00000000000000package system import "golang.org/x/sys/unix" // Returns a []byte slice if the xattr is set and nil otherwise // Requires path and its attribute as arguments func Lgetxattr(path string, attr string) ([]byte, error) { var sz int // Start with a 128 length byte array dest := make([]byte, 128) sz, errno := unix.Lgetxattr(path, attr, dest) switch { case errno == unix.ENODATA: return nil, errno case errno == unix.ENOTSUP: return nil, errno case errno == unix.ERANGE: // 128 byte array might just not be good enough, // A dummy buffer is used to get the real size // of the xattrs on disk sz, errno = unix.Lgetxattr(path, attr, []byte{}) if errno != nil { return nil, errno } dest = make([]byte, sz) sz, errno = unix.Lgetxattr(path, attr, dest) if errno != nil { return nil, errno } case errno != nil: return nil, errno } return dest[:sz], nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/user/000077500000000000000000000000001315410276000263735ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/user/MAINTAINERS000066400000000000000000000001301315410276000300620ustar00rootroot00000000000000Tianon Gravi (@tianon) Aleksa Sarai (@cyphar) cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/user/lookup.go000066400000000000000000000050121315410276000302310ustar00rootroot00000000000000package user import ( "errors" ) var ( // The current operating system does not provide the required data for user lookups. ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data") // No matching entries found in file. ErrNoPasswdEntries = errors.New("no matching entries in passwd file") ErrNoGroupEntries = errors.New("no matching entries in group file") ) func lookupUser(filter func(u User) bool) (User, error) { // Get operating system-specific passwd reader-closer. passwd, err := GetPasswd() if err != nil { return User{}, err } defer passwd.Close() // Get the users. users, err := ParsePasswdFilter(passwd, filter) if err != nil { return User{}, err } // No user entries found. if len(users) == 0 { return User{}, ErrNoPasswdEntries } // Assume the first entry is the "correct" one. return users[0], nil } // LookupUser looks up a user by their username in /etc/passwd. If the user // cannot be found (or there is no /etc/passwd file on the filesystem), then // LookupUser returns an error. func LookupUser(username string) (User, error) { return lookupUser(func(u User) bool { return u.Name == username }) } // LookupUid looks up a user by their user id in /etc/passwd. If the user cannot // be found (or there is no /etc/passwd file on the filesystem), then LookupId // returns an error. func LookupUid(uid int) (User, error) { return lookupUser(func(u User) bool { return u.Uid == uid }) } func lookupGroup(filter func(g Group) bool) (Group, error) { // Get operating system-specific group reader-closer. group, err := GetGroup() if err != nil { return Group{}, err } defer group.Close() // Get the users. groups, err := ParseGroupFilter(group, filter) if err != nil { return Group{}, err } // No user entries found. if len(groups) == 0 { return Group{}, ErrNoGroupEntries } // Assume the first entry is the "correct" one. return groups[0], nil } // LookupGroup looks up a group by its name in /etc/group. If the group cannot // be found (or there is no /etc/group file on the filesystem), then LookupGroup // returns an error. func LookupGroup(groupname string) (Group, error) { return lookupGroup(func(g Group) bool { return g.Name == groupname }) } // LookupGid looks up a group by its group id in /etc/group. If the group cannot // be found (or there is no /etc/group file on the filesystem), then LookupGid // returns an error. func LookupGid(gid int) (Group, error) { return lookupGroup(func(g Group) bool { return g.Gid == gid }) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go000066400000000000000000000021361315410276000313000ustar00rootroot00000000000000// +build darwin dragonfly freebsd linux netbsd openbsd solaris package user import ( "io" "os" "golang.org/x/sys/unix" ) // Unix-specific path to the passwd and group formatted files. const ( unixPasswdPath = "/etc/passwd" unixGroupPath = "/etc/group" ) func GetPasswdPath() (string, error) { return unixPasswdPath, nil } func GetPasswd() (io.ReadCloser, error) { return os.Open(unixPasswdPath) } func GetGroupPath() (string, error) { return unixGroupPath, nil } func GetGroup() (io.ReadCloser, error) { return os.Open(unixGroupPath) } // CurrentUser looks up the current user by their user id in /etc/passwd. If the // user cannot be found (or there is no /etc/passwd file on the filesystem), // then CurrentUser returns an error. func CurrentUser() (User, error) { return LookupUid(unix.Getuid()) } // CurrentGroup looks up the current user's group by their primary group id's // entry in /etc/passwd. If the group cannot be found (or there is no // /etc/group file on the filesystem), then CurrentGroup returns an error. func CurrentGroup() (Group, error) { return LookupGid(unix.Getgid()) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go000066400000000000000000000017051315410276000327060ustar00rootroot00000000000000// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris package user import ( "io" "syscall" ) func GetPasswdPath() (string, error) { return "", ErrUnsupported } func GetPasswd() (io.ReadCloser, error) { return nil, ErrUnsupported } func GetGroupPath() (string, error) { return "", ErrUnsupported } func GetGroup() (io.ReadCloser, error) { return nil, ErrUnsupported } // CurrentUser looks up the current user by their user id in /etc/passwd. If the // user cannot be found (or there is no /etc/passwd file on the filesystem), // then CurrentUser returns an error. func CurrentUser() (User, error) { return LookupUid(syscall.Getuid()) } // CurrentGroup looks up the current user's group by their primary group id's // entry in /etc/passwd. If the group cannot be found (or there is no // /etc/group file on the filesystem), then CurrentGroup returns an error. func CurrentGroup() (Group, error) { return LookupGid(syscall.Getgid()) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/user/user.go000066400000000000000000000254621315410276000277110ustar00rootroot00000000000000package user import ( "bufio" "fmt" "io" "os" "strconv" "strings" ) const ( minId = 0 maxId = 1<<31 - 1 //for 32-bit systems compatibility ) var ( ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minId, maxId) ) type User struct { Name string Pass string Uid int Gid int Gecos string Home string Shell string } type Group struct { Name string Pass string Gid int List []string } func parseLine(line string, v ...interface{}) { if line == "" { return } parts := strings.Split(line, ":") for i, p := range parts { // Ignore cases where we don't have enough fields to populate the arguments. // Some configuration files like to misbehave. if len(v) <= i { break } // Use the type of the argument to figure out how to parse it, scanf() style. // This is legit. switch e := v[i].(type) { case *string: *e = p case *int: // "numbers", with conversion errors ignored because of some misbehaving configuration files. *e, _ = strconv.Atoi(p) case *[]string: // Comma-separated lists. if p != "" { *e = strings.Split(p, ",") } else { *e = []string{} } default: // Someone goof'd when writing code using this function. Scream so they can hear us. panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e)) } } } func ParsePasswdFile(path string) ([]User, error) { passwd, err := os.Open(path) if err != nil { return nil, err } defer passwd.Close() return ParsePasswd(passwd) } func ParsePasswd(passwd io.Reader) ([]User, error) { return ParsePasswdFilter(passwd, nil) } func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) { passwd, err := os.Open(path) if err != nil { return nil, err } defer passwd.Close() return ParsePasswdFilter(passwd, filter) } func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { if r == nil { return nil, fmt.Errorf("nil source for passwd-formatted data") } var ( s = bufio.NewScanner(r) out = []User{} ) for s.Scan() { if err := s.Err(); err != nil { return nil, err } line := strings.TrimSpace(s.Text()) if line == "" { continue } // see: man 5 passwd // name:password:UID:GID:GECOS:directory:shell // Name:Pass:Uid:Gid:Gecos:Home:Shell // root:x:0:0:root:/root:/bin/bash // adm:x:3:4:adm:/var/adm:/bin/false p := User{} parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell) if filter == nil || filter(p) { out = append(out, p) } } return out, nil } func ParseGroupFile(path string) ([]Group, error) { group, err := os.Open(path) if err != nil { return nil, err } defer group.Close() return ParseGroup(group) } func ParseGroup(group io.Reader) ([]Group, error) { return ParseGroupFilter(group, nil) } func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) { group, err := os.Open(path) if err != nil { return nil, err } defer group.Close() return ParseGroupFilter(group, filter) } func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { if r == nil { return nil, fmt.Errorf("nil source for group-formatted data") } var ( s = bufio.NewScanner(r) out = []Group{} ) for s.Scan() { if err := s.Err(); err != nil { return nil, err } text := s.Text() if text == "" { continue } // see: man 5 group // group_name:password:GID:user_list // Name:Pass:Gid:List // root:x:0:root // adm:x:4:root,adm,daemon p := Group{} parseLine(text, &p.Name, &p.Pass, &p.Gid, &p.List) if filter == nil || filter(p) { out = append(out, p) } } return out, nil } type ExecUser struct { Uid int Gid int Sgids []int Home string } // GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the // given file paths and uses that data as the arguments to GetExecUser. If the // files cannot be opened for any reason, the error is ignored and a nil // io.Reader is passed instead. func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) { var passwd, group io.Reader if passwdFile, err := os.Open(passwdPath); err == nil { passwd = passwdFile defer passwdFile.Close() } if groupFile, err := os.Open(groupPath); err == nil { group = groupFile defer groupFile.Close() } return GetExecUser(userSpec, defaults, passwd, group) } // GetExecUser parses a user specification string (using the passwd and group // readers as sources for /etc/passwd and /etc/group data, respectively). In // the case of blank fields or missing data from the sources, the values in // defaults is used. // // GetExecUser will return an error if a user or group literal could not be // found in any entry in passwd and group respectively. // // Examples of valid user specifications are: // * "" // * "user" // * "uid" // * "user:group" // * "uid:gid // * "user:gid" // * "uid:group" // // It should be noted that if you specify a numeric user or group id, they will // not be evaluated as usernames (only the metadata will be filled). So attempting // to parse a user with user.Name = "1337" will produce the user with a UID of // 1337. func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) { if defaults == nil { defaults = new(ExecUser) } // Copy over defaults. user := &ExecUser{ Uid: defaults.Uid, Gid: defaults.Gid, Sgids: defaults.Sgids, Home: defaults.Home, } // Sgids slice *cannot* be nil. if user.Sgids == nil { user.Sgids = []int{} } // Allow for userArg to have either "user" syntax, or optionally "user:group" syntax var userArg, groupArg string parseLine(userSpec, &userArg, &groupArg) // Convert userArg and groupArg to be numeric, so we don't have to execute // Atoi *twice* for each iteration over lines. uidArg, uidErr := strconv.Atoi(userArg) gidArg, gidErr := strconv.Atoi(groupArg) // Find the matching user. users, err := ParsePasswdFilter(passwd, func(u User) bool { if userArg == "" { // Default to current state of the user. return u.Uid == user.Uid } if uidErr == nil { // If the userArg is numeric, always treat it as a UID. return uidArg == u.Uid } return u.Name == userArg }) // If we can't find the user, we have to bail. if err != nil && passwd != nil { if userArg == "" { userArg = strconv.Itoa(user.Uid) } return nil, fmt.Errorf("unable to find user %s: %v", userArg, err) } var matchedUserName string if len(users) > 0 { // First match wins, even if there's more than one matching entry. matchedUserName = users[0].Name user.Uid = users[0].Uid user.Gid = users[0].Gid user.Home = users[0].Home } else if userArg != "" { // If we can't find a user with the given username, the only other valid // option is if it's a numeric username with no associated entry in passwd. if uidErr != nil { // Not numeric. return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries) } user.Uid = uidArg // Must be inside valid uid range. if user.Uid < minId || user.Uid > maxId { return nil, ErrRange } // Okay, so it's numeric. We can just roll with this. } // On to the groups. If we matched a username, we need to do this because of // the supplementary group IDs. if groupArg != "" || matchedUserName != "" { groups, err := ParseGroupFilter(group, func(g Group) bool { // If the group argument isn't explicit, we'll just search for it. if groupArg == "" { // Check if user is a member of this group. for _, u := range g.List { if u == matchedUserName { return true } } return false } if gidErr == nil { // If the groupArg is numeric, always treat it as a GID. return gidArg == g.Gid } return g.Name == groupArg }) if err != nil && group != nil { return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err) } // Only start modifying user.Gid if it is in explicit form. if groupArg != "" { if len(groups) > 0 { // First match wins, even if there's more than one matching entry. user.Gid = groups[0].Gid } else { // If we can't find a group with the given name, the only other valid // option is if it's a numeric group name with no associated entry in group. if gidErr != nil { // Not numeric. return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries) } user.Gid = gidArg // Must be inside valid gid range. if user.Gid < minId || user.Gid > maxId { return nil, ErrRange } // Okay, so it's numeric. We can just roll with this. } } else if len(groups) > 0 { // Supplementary group ids only make sense if in the implicit form. user.Sgids = make([]int, len(groups)) for i, group := range groups { user.Sgids[i] = group.Gid } } } return user, nil } // GetAdditionalGroups looks up a list of groups by name or group id // against the given /etc/group formatted data. If a group name cannot // be found, an error will be returned. If a group id cannot be found, // or the given group data is nil, the id will be returned as-is // provided it is in the legal range. func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) { var groups = []Group{} if group != nil { var err error groups, err = ParseGroupFilter(group, func(g Group) bool { for _, ag := range additionalGroups { if g.Name == ag || strconv.Itoa(g.Gid) == ag { return true } } return false }) if err != nil { return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err) } } gidMap := make(map[int]struct{}) for _, ag := range additionalGroups { var found bool for _, g := range groups { // if we found a matched group either by name or gid, take the // first matched as correct if g.Name == ag || strconv.Itoa(g.Gid) == ag { if _, ok := gidMap[g.Gid]; !ok { gidMap[g.Gid] = struct{}{} found = true break } } } // we asked for a group but didn't find it. let's check to see // if we wanted a numeric group if !found { gid, err := strconv.Atoi(ag) if err != nil { return nil, fmt.Errorf("Unable to find group %s", ag) } // Ensure gid is inside gid range. if gid < minId || gid > maxId { return nil, ErrRange } gidMap[gid] = struct{}{} } } gids := []int{} for gid := range gidMap { gids = append(gids, gid) } return gids, nil } // GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups // that opens the groupPath given and gives it as an argument to // GetAdditionalGroups. func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) { var group io.Reader if groupFile, err := os.Open(groupPath); err == nil { group = groupFile defer groupFile.Close() } return GetAdditionalGroups(additionalGroups, group) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/utils/000077500000000000000000000000001315410276000265555ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go000066400000000000000000000055231315410276000300420ustar00rootroot00000000000000// +build linux package utils /* * Copyright 2016, 2017 SUSE LLC * * 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. */ import ( "fmt" "os" "golang.org/x/sys/unix" ) // MaxSendfdLen is the maximum length of the name of a file descriptor being // sent using SendFd. The name of the file handle returned by RecvFd will never // be larger than this value. const MaxNameLen = 4096 // oobSpace is the size of the oob slice required to store a single FD. Note // that unix.UnixRights appears to make the assumption that fd is always int32, // so sizeof(fd) = 4. var oobSpace = unix.CmsgSpace(4) // RecvFd waits for a file descriptor to be sent over the given AF_UNIX // socket. The file name of the remote file descriptor will be recreated // locally (it is sent as non-auxiliary data in the same payload). func RecvFd(socket *os.File) (*os.File, error) { // For some reason, unix.Recvmsg uses the length rather than the capacity // when passing the msg_controllen and other attributes to recvmsg. So we // have to actually set the length. name := make([]byte, MaxNameLen) oob := make([]byte, oobSpace) sockfd := socket.Fd() n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0) if err != nil { return nil, err } if n >= MaxNameLen || oobn != oobSpace { return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn) } // Truncate. name = name[:n] oob = oob[:oobn] scms, err := unix.ParseSocketControlMessage(oob) if err != nil { return nil, err } if len(scms) != 1 { return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms)) } scm := scms[0] fds, err := unix.ParseUnixRights(&scm) if err != nil { return nil, err } if len(fds) != 1 { return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds)) } fd := uintptr(fds[0]) return os.NewFile(fd, string(name)), nil } // SendFd sends a file descriptor over the given AF_UNIX socket. In // addition, the file.Name() of the given file will also be sent as // non-auxiliary data in the same payload (allowing to send contextual // information for a file descriptor). func SendFd(socket, file *os.File) error { name := []byte(file.Name()) if len(name) >= MaxNameLen { return fmt.Errorf("sendfd: filename too long: %s", file.Name()) } oob := unix.UnixRights(int(file.Fd())) return unix.Sendmsg(int(socket.Fd()), name, oob, nil, 0) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go000066400000000000000000000067251315410276000302560ustar00rootroot00000000000000package utils import ( "crypto/rand" "encoding/hex" "encoding/json" "io" "os" "path/filepath" "strings" "unsafe" "golang.org/x/sys/unix" ) const ( exitSignalOffset = 128 ) // GenerateRandomName returns a new name joined with a prefix. This size // specified is used to truncate the randomly generated value func GenerateRandomName(prefix string, size int) (string, error) { id := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, id); err != nil { return "", err } if size > 64 { size = 64 } return prefix + hex.EncodeToString(id)[:size], nil } // ResolveRootfs ensures that the current working directory is // not a symlink and returns the absolute path to the rootfs func ResolveRootfs(uncleanRootfs string) (string, error) { rootfs, err := filepath.Abs(uncleanRootfs) if err != nil { return "", err } return filepath.EvalSymlinks(rootfs) } // ExitStatus returns the correct exit status for a process based on if it // was signaled or exited cleanly func ExitStatus(status unix.WaitStatus) int { if status.Signaled() { return exitSignalOffset + int(status.Signal()) } return status.ExitStatus() } // WriteJSON writes the provided struct v to w using standard json marshaling func WriteJSON(w io.Writer, v interface{}) error { data, err := json.Marshal(v) if err != nil { return err } _, err = w.Write(data) return err } // CleanPath makes a path safe for use with filepath.Join. This is done by not // only cleaning the path, but also (if the path is relative) adding a leading // '/' and cleaning it (then removing the leading '/'). This ensures that a // path resulting from prepending another path will always resolve to lexically // be a subdirectory of the prefixed path. This is all done lexically, so paths // that include symlinks won't be safe as a result of using CleanPath. func CleanPath(path string) string { // Deal with empty strings nicely. if path == "" { return "" } // Ensure that all paths are cleaned (especially problematic ones like // "/../../../../../" which can cause lots of issues). path = filepath.Clean(path) // If the path isn't absolute, we need to do more processing to fix paths // such as "../../../..//some/path". We also shouldn't convert absolute // paths to relative ones. if !filepath.IsAbs(path) { path = filepath.Clean(string(os.PathSeparator) + path) // This can't fail, as (by definition) all paths are relative to root. path, _ = filepath.Rel(string(os.PathSeparator), path) } // Clean the path again for good measure. return filepath.Clean(path) } // SearchLabels searches a list of key-value pairs for the provided key and // returns the corresponding value. The pairs must be separated with '='. func SearchLabels(labels []string, query string) string { for _, l := range labels { parts := strings.SplitN(l, "=", 2) if len(parts) < 2 { continue } if parts[0] == query { return parts[1] } } return "" } // Annotations returns the bundle path and user defined annotations from the // libcontainer state. We need to remove the bundle because that is a label // added by libcontainer. func Annotations(labels []string) (bundle string, userAnnotations map[string]string) { userAnnotations = make(map[string]string) for _, l := range labels { parts := strings.SplitN(l, "=", 2) if len(parts) < 2 { continue } if parts[0] == "bundle" { bundle = parts[1] } else { userAnnotations[parts[0]] = parts[1] } } return } func GetIntSize() int { return int(unsafe.Sizeof(1)) } cadvisor-0.27.1/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go000066400000000000000000000020771315410276000313150ustar00rootroot00000000000000// +build !windows package utils import ( "io/ioutil" "os" "strconv" "golang.org/x/sys/unix" ) func CloseExecFrom(minFd int) error { fdList, err := ioutil.ReadDir("/proc/self/fd") if err != nil { return err } for _, fi := range fdList { fd, err := strconv.Atoi(fi.Name()) if err != nil { // ignore non-numeric file names continue } if fd < minFd { // ignore descriptors lower than our specified minimum continue } // intentionally ignore errors from unix.CloseOnExec unix.CloseOnExec(fd) // the cases where this might fail are basically file descriptors that have already been closed (including and especially the one that was created when ioutil.ReadDir did the "opendir" syscall) } return nil } // NewSockPair returns a new unix socket pair func NewSockPair(name string) (parent *os.File, child *os.File, err error) { fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) if err != nil { return nil, nil, err } return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil } cadvisor-0.27.1/vendor/github.com/opencontainers/runtime-spec/000077500000000000000000000000001315410276000244105ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runtime-spec/LICENSE000066400000000000000000000250171315410276000254220ustar00rootroot00000000000000 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 Copyright 2015 The Linux Foundation. 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. cadvisor-0.27.1/vendor/github.com/opencontainers/runtime-spec/specs-go/000077500000000000000000000000001315410276000261305ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go000066400000000000000000000567051315410276000277410ustar00rootroot00000000000000package specs import "os" // Spec is the base configuration for the container. type Spec struct { // Version of the Open Container Runtime Specification with which the bundle complies. Version string `json:"ociVersion"` // Platform specifies the configuration's target platform. Platform Platform `json:"platform"` // Process configures the container process. Process Process `json:"process"` // Root configures the container's root filesystem. Root Root `json:"root"` // Hostname configures the container's hostname. Hostname string `json:"hostname,omitempty"` // Mounts configures additional mounts (on top of Root). Mounts []Mount `json:"mounts,omitempty"` // Hooks configures callbacks for container lifecycle events. Hooks *Hooks `json:"hooks,omitempty"` // Annotations contains arbitrary metadata for the container. Annotations map[string]string `json:"annotations,omitempty"` // Linux is platform specific configuration for Linux based containers. Linux *Linux `json:"linux,omitempty" platform:"linux"` // Solaris is platform specific configuration for Solaris containers. Solaris *Solaris `json:"solaris,omitempty" platform:"solaris"` // Windows is platform specific configuration for Windows based containers, including Hyper-V containers. Windows *Windows `json:"windows,omitempty" platform:"windows"` } // Process contains information to start a specific application inside the container. type Process struct { // Terminal creates an interactive terminal for the container. Terminal bool `json:"terminal,omitempty"` // ConsoleSize specifies the size of the console. ConsoleSize Box `json:"consoleSize,omitempty"` // User specifies user information for the process. User User `json:"user"` // Args specifies the binary and arguments for the application to execute. Args []string `json:"args"` // Env populates the process environment for the process. Env []string `json:"env,omitempty"` // Cwd is the current working directory for the process and must be // relative to the container's root. Cwd string `json:"cwd"` // Capabilities are Linux capabilities that are kept for the process. Capabilities *LinuxCapabilities `json:"capabilities,omitempty" platform:"linux"` // Rlimits specifies rlimit options to apply to the process. Rlimits []LinuxRlimit `json:"rlimits,omitempty" platform:"linux"` // NoNewPrivileges controls whether additional privileges could be gained by processes in the container. NoNewPrivileges bool `json:"noNewPrivileges,omitempty" platform:"linux"` // ApparmorProfile specifies the apparmor profile for the container. ApparmorProfile string `json:"apparmorProfile,omitempty" platform:"linux"` // SelinuxLabel specifies the selinux context that the container process is run as. SelinuxLabel string `json:"selinuxLabel,omitempty" platform:"linux"` } // LinuxCapabilities specifies the whitelist of capabilities that are kept for a process. // http://man7.org/linux/man-pages/man7/capabilities.7.html type LinuxCapabilities struct { // Bounding is the set of capabilities checked by the kernel. Bounding []string `json:"bounding,omitempty" platform:"linux"` // Effective is the set of capabilities checked by the kernel. Effective []string `json:"effective,omitempty" platform:"linux"` // Inheritable is the capabilities preserved across execve. Inheritable []string `json:"inheritable,omitempty" platform:"linux"` // Permitted is the limiting superset for effective capabilities. Permitted []string `json:"permitted,omitempty" platform:"linux"` // Ambient is the ambient set of capabilities that are kept. Ambient []string `json:"ambient,omitempty" platform:"linux"` } // Box specifies dimensions of a rectangle. Used for specifying the size of a console. type Box struct { // Height is the vertical dimension of a box. Height uint `json:"height"` // Width is the horizontal dimension of a box. Width uint `json:"width"` } // User specifies specific user (and group) information for the container process. type User struct { // UID is the user id. UID uint32 `json:"uid" platform:"linux,solaris"` // GID is the group id. GID uint32 `json:"gid" platform:"linux,solaris"` // AdditionalGids are additional group ids set for the container's process. AdditionalGids []uint32 `json:"additionalGids,omitempty" platform:"linux,solaris"` // Username is the user name. Username string `json:"username,omitempty" platform:"windows"` } // Root contains information about the container's root filesystem on the host. type Root struct { // Path is the absolute path to the container's root filesystem. Path string `json:"path"` // Readonly makes the root filesystem for the container readonly before the process is executed. Readonly bool `json:"readonly,omitempty"` } // Platform specifies OS and arch information for the host system that the container // is created for. type Platform struct { // OS is the operating system. OS string `json:"os"` // Arch is the architecture Arch string `json:"arch"` } // Mount specifies a mount for a container. type Mount struct { // Destination is the path where the mount will be placed relative to the container's root. The path and child directories MUST exist, a runtime MUST NOT create directories automatically to a mount point. Destination string `json:"destination"` // Type specifies the mount kind. Type string `json:"type,omitempty"` // Source specifies the source path of the mount. In the case of bind mounts on // Linux based systems this would be the file on the host. Source string `json:"source,omitempty"` // Options are fstab style mount options. Options []string `json:"options,omitempty"` } // Hook specifies a command that is run at a particular event in the lifecycle of a container type Hook struct { Path string `json:"path"` Args []string `json:"args,omitempty"` Env []string `json:"env,omitempty"` Timeout *int `json:"timeout,omitempty"` } // Hooks for container setup and teardown type Hooks struct { // Prestart is a list of hooks to be run before the container process is executed. // On Linux, they are run after the container namespaces are created. Prestart []Hook `json:"prestart,omitempty"` // Poststart is a list of hooks to be run after the container process is started. Poststart []Hook `json:"poststart,omitempty"` // Poststop is a list of hooks to be run after the container process exits. Poststop []Hook `json:"poststop,omitempty"` } // Linux contains platform specific configuration for Linux based containers. type Linux struct { // UIDMapping specifies user mappings for supporting user namespaces on Linux. UIDMappings []LinuxIDMapping `json:"uidMappings,omitempty"` // GIDMapping specifies group mappings for supporting user namespaces on Linux. GIDMappings []LinuxIDMapping `json:"gidMappings,omitempty"` // Sysctl are a set of key value pairs that are set for the container on start Sysctl map[string]string `json:"sysctl,omitempty"` // Resources contain cgroup information for handling resource constraints // for the container Resources *LinuxResources `json:"resources,omitempty"` // CgroupsPath specifies the path to cgroups that are created and/or joined by the container. // The path is expected to be relative to the cgroups mountpoint. // If resources are specified, the cgroups at CgroupsPath will be updated based on resources. CgroupsPath string `json:"cgroupsPath,omitempty"` // Namespaces contains the namespaces that are created and/or joined by the container Namespaces []LinuxNamespace `json:"namespaces,omitempty"` // Devices are a list of device nodes that are created for the container Devices []LinuxDevice `json:"devices,omitempty"` // Seccomp specifies the seccomp security settings for the container. Seccomp *LinuxSeccomp `json:"seccomp,omitempty"` // RootfsPropagation is the rootfs mount propagation mode for the container. RootfsPropagation string `json:"rootfsPropagation,omitempty"` // MaskedPaths masks over the provided paths inside the container. MaskedPaths []string `json:"maskedPaths,omitempty"` // ReadonlyPaths sets the provided paths as RO inside the container. ReadonlyPaths []string `json:"readonlyPaths,omitempty"` // MountLabel specifies the selinux context for the mounts in the container. MountLabel string `json:"mountLabel,omitempty"` // IntelRdt contains Intel Resource Director Technology (RDT) information // for handling resource constraints (e.g., L3 cache) for the container IntelRdt *LinuxIntelRdt `json:"intelRdt,omitempty"` } // LinuxNamespace is the configuration for a Linux namespace type LinuxNamespace struct { // Type is the type of Linux namespace Type LinuxNamespaceType `json:"type"` // Path is a path to an existing namespace persisted on disk that can be joined // and is of the same type Path string `json:"path,omitempty"` } // LinuxNamespaceType is one of the Linux namespaces type LinuxNamespaceType string const ( // PIDNamespace for isolating process IDs PIDNamespace LinuxNamespaceType = "pid" // NetworkNamespace for isolating network devices, stacks, ports, etc NetworkNamespace = "network" // MountNamespace for isolating mount points MountNamespace = "mount" // IPCNamespace for isolating System V IPC, POSIX message queues IPCNamespace = "ipc" // UTSNamespace for isolating hostname and NIS domain name UTSNamespace = "uts" // UserNamespace for isolating user and group IDs UserNamespace = "user" // CgroupNamespace for isolating cgroup hierarchies CgroupNamespace = "cgroup" ) // LinuxIDMapping specifies UID/GID mappings type LinuxIDMapping struct { // HostID is the starting UID/GID on the host to be mapped to 'ContainerID' HostID uint32 `json:"hostID"` // ContainerID is the starting UID/GID in the container ContainerID uint32 `json:"containerID"` // Size is the number of IDs to be mapped Size uint32 `json:"size"` } // LinuxRlimit type and restrictions type LinuxRlimit struct { // Type of the rlimit to set Type string `json:"type"` // Hard is the hard limit for the specified type Hard uint64 `json:"hard"` // Soft is the soft limit for the specified type Soft uint64 `json:"soft"` } // LinuxHugepageLimit structure corresponds to limiting kernel hugepages type LinuxHugepageLimit struct { // Pagesize is the hugepage size Pagesize string `json:"pageSize"` // Limit is the limit of "hugepagesize" hugetlb usage Limit uint64 `json:"limit"` } // LinuxInterfacePriority for network interfaces type LinuxInterfacePriority struct { // Name is the name of the network interface Name string `json:"name"` // Priority for the interface Priority uint32 `json:"priority"` } // linuxBlockIODevice holds major:minor format supported in blkio cgroup type linuxBlockIODevice struct { // Major is the device's major number. Major int64 `json:"major"` // Minor is the device's minor number. Minor int64 `json:"minor"` } // LinuxWeightDevice struct holds a `major:minor weight` pair for blkioWeightDevice type LinuxWeightDevice struct { linuxBlockIODevice // Weight is the bandwidth rate for the device, range is from 10 to 1000 Weight *uint16 `json:"weight,omitempty"` // LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only LeafWeight *uint16 `json:"leafWeight,omitempty"` } // LinuxThrottleDevice struct holds a `major:minor rate_per_second` pair type LinuxThrottleDevice struct { linuxBlockIODevice // Rate is the IO rate limit per cgroup per device Rate uint64 `json:"rate"` } // LinuxBlockIO for Linux cgroup 'blkio' resource management type LinuxBlockIO struct { // Specifies per cgroup weight, range is from 10 to 1000 Weight *uint16 `json:"blkioWeight,omitempty"` // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only LeafWeight *uint16 `json:"blkioLeafWeight,omitempty"` // Weight per cgroup per device, can override BlkioWeight WeightDevice []LinuxWeightDevice `json:"blkioWeightDevice,omitempty"` // IO read rate limit per cgroup per device, bytes per second ThrottleReadBpsDevice []LinuxThrottleDevice `json:"blkioThrottleReadBpsDevice,omitempty"` // IO write rate limit per cgroup per device, bytes per second ThrottleWriteBpsDevice []LinuxThrottleDevice `json:"blkioThrottleWriteBpsDevice,omitempty"` // IO read rate limit per cgroup per device, IO per second ThrottleReadIOPSDevice []LinuxThrottleDevice `json:"blkioThrottleReadIOPSDevice,omitempty"` // IO write rate limit per cgroup per device, IO per second ThrottleWriteIOPSDevice []LinuxThrottleDevice `json:"blkioThrottleWriteIOPSDevice,omitempty"` } // LinuxMemory for Linux cgroup 'memory' resource management type LinuxMemory struct { // Memory limit (in bytes). Limit *uint64 `json:"limit,omitempty"` // Memory reservation or soft_limit (in bytes). Reservation *uint64 `json:"reservation,omitempty"` // Total memory limit (memory + swap). Swap *uint64 `json:"swap,omitempty"` // Kernel memory limit (in bytes). Kernel *uint64 `json:"kernel,omitempty"` // Kernel memory limit for tcp (in bytes) KernelTCP *uint64 `json:"kernelTCP,omitempty"` // How aggressive the kernel will swap memory pages. Range from 0 to 100. Swappiness *uint64 `json:"swappiness,omitempty"` } // LinuxCPU for Linux cgroup 'cpu' resource management type LinuxCPU struct { // CPU shares (relative weight (ratio) vs. other cgroups with cpu shares). Shares *uint64 `json:"shares,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. Quota *int64 `json:"quota,omitempty"` // CPU period to be used for hardcapping (in usecs). Period *uint64 `json:"period,omitempty"` // How much time realtime scheduling may use (in usecs). RealtimeRuntime *int64 `json:"realtimeRuntime,omitempty"` // CPU period to be used for realtime scheduling (in usecs). RealtimePeriod *uint64 `json:"realtimePeriod,omitempty"` // CPUs to use within the cpuset. Default is to use any CPU available. Cpus string `json:"cpus,omitempty"` // List of memory nodes in the cpuset. Default is to use any available memory node. Mems string `json:"mems,omitempty"` } // LinuxPids for Linux cgroup 'pids' resource management (Linux 4.3) type LinuxPids struct { // Maximum number of PIDs. Default is "no limit". Limit int64 `json:"limit"` } // LinuxNetwork identification and priority configuration type LinuxNetwork struct { // Set class identifier for container's network packets ClassID *uint32 `json:"classID,omitempty"` // Set priority of network traffic for container Priorities []LinuxInterfacePriority `json:"priorities,omitempty"` } // LinuxResources has container runtime resource constraints type LinuxResources struct { // Devices configures the device whitelist. Devices []LinuxDeviceCgroup `json:"devices,omitempty"` // DisableOOMKiller disables the OOM killer for out of memory conditions DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"` // Specify an oom_score_adj for the container. OOMScoreAdj *int `json:"oomScoreAdj,omitempty"` // Memory restriction configuration Memory *LinuxMemory `json:"memory,omitempty"` // CPU resource restriction configuration CPU *LinuxCPU `json:"cpu,omitempty"` // Task resource restriction configuration. Pids *LinuxPids `json:"pids,omitempty"` // BlockIO restriction configuration BlockIO *LinuxBlockIO `json:"blockIO,omitempty"` // Hugetlb limit (in bytes) HugepageLimits []LinuxHugepageLimit `json:"hugepageLimits,omitempty"` // Network restriction configuration Network *LinuxNetwork `json:"network,omitempty"` } // LinuxDevice represents the mknod information for a Linux special device file type LinuxDevice struct { // Path to the device. Path string `json:"path"` // Device type, block, char, etc. Type string `json:"type"` // Major is the device's major number. Major int64 `json:"major"` // Minor is the device's minor number. Minor int64 `json:"minor"` // FileMode permission bits for the device. FileMode *os.FileMode `json:"fileMode,omitempty"` // UID of the device. UID *uint32 `json:"uid,omitempty"` // Gid of the device. GID *uint32 `json:"gid,omitempty"` } // LinuxDeviceCgroup represents a device rule for the whitelist controller type LinuxDeviceCgroup struct { // Allow or deny Allow bool `json:"allow"` // Device type, block, char, etc. Type string `json:"type,omitempty"` // Major is the device's major number. Major *int64 `json:"major,omitempty"` // Minor is the device's minor number. Minor *int64 `json:"minor,omitempty"` // Cgroup access permissions format, rwm. Access string `json:"access,omitempty"` } // Solaris contains platform specific configuration for Solaris application containers. type Solaris struct { // SMF FMRI which should go "online" before we start the container process. Milestone string `json:"milestone,omitempty"` // Maximum set of privileges any process in this container can obtain. LimitPriv string `json:"limitpriv,omitempty"` // The maximum amount of shared memory allowed for this container. MaxShmMemory string `json:"maxShmMemory,omitempty"` // Specification for automatic creation of network resources for this container. Anet []SolarisAnet `json:"anet,omitempty"` // Set limit on the amount of CPU time that can be used by container. CappedCPU *SolarisCappedCPU `json:"cappedCPU,omitempty"` // The physical and swap caps on the memory that can be used by this container. CappedMemory *SolarisCappedMemory `json:"cappedMemory,omitempty"` } // SolarisCappedCPU allows users to set limit on the amount of CPU time that can be used by container. type SolarisCappedCPU struct { Ncpus string `json:"ncpus,omitempty"` } // SolarisCappedMemory allows users to set the physical and swap caps on the memory that can be used by this container. type SolarisCappedMemory struct { Physical string `json:"physical,omitempty"` Swap string `json:"swap,omitempty"` } // SolarisAnet provides the specification for automatic creation of network resources for this container. type SolarisAnet struct { // Specify a name for the automatically created VNIC datalink. Linkname string `json:"linkname,omitempty"` // Specify the link over which the VNIC will be created. Lowerlink string `json:"lowerLink,omitempty"` // The set of IP addresses that the container can use. Allowedaddr string `json:"allowedAddress,omitempty"` // Specifies whether allowedAddress limitation is to be applied to the VNIC. Configallowedaddr string `json:"configureAllowedAddress,omitempty"` // The value of the optional default router. Defrouter string `json:"defrouter,omitempty"` // Enable one or more types of link protection. Linkprotection string `json:"linkProtection,omitempty"` // Set the VNIC's macAddress Macaddress string `json:"macAddress,omitempty"` } // Windows defines the runtime configuration for Windows based containers, including Hyper-V containers. type Windows struct { // Resources contains information for handling resource constraints for the container. Resources *WindowsResources `json:"resources,omitempty"` } // WindowsResources has container runtime resource constraints for containers running on Windows. type WindowsResources struct { // Memory restriction configuration. Memory *WindowsMemoryResources `json:"memory,omitempty"` // CPU resource restriction configuration. CPU *WindowsCPUResources `json:"cpu,omitempty"` // Storage restriction configuration. Storage *WindowsStorageResources `json:"storage,omitempty"` // Network restriction configuration. Network *WindowsNetworkResources `json:"network,omitempty"` } // WindowsMemoryResources contains memory resource management settings. type WindowsMemoryResources struct { // Memory limit in bytes. Limit *uint64 `json:"limit,omitempty"` // Memory reservation in bytes. Reservation *uint64 `json:"reservation,omitempty"` } // WindowsCPUResources contains CPU resource management settings. type WindowsCPUResources struct { // Number of CPUs available to the container. Count *uint64 `json:"count,omitempty"` // CPU shares (relative weight to other containers with cpu shares). Range is from 1 to 10000. Shares *uint16 `json:"shares,omitempty"` // Percent of available CPUs usable by the container. Percent *uint8 `json:"percent,omitempty"` } // WindowsStorageResources contains storage resource management settings. type WindowsStorageResources struct { // Specifies maximum Iops for the system drive. Iops *uint64 `json:"iops,omitempty"` // Specifies maximum bytes per second for the system drive. Bps *uint64 `json:"bps,omitempty"` // Sandbox size specifies the minimum size of the system drive in bytes. SandboxSize *uint64 `json:"sandboxSize,omitempty"` } // WindowsNetworkResources contains network resource management settings. type WindowsNetworkResources struct { // EgressBandwidth is the maximum egress bandwidth in bytes per second. EgressBandwidth *uint64 `json:"egressBandwidth,omitempty"` } // LinuxSeccomp represents syscall restrictions type LinuxSeccomp struct { DefaultAction LinuxSeccompAction `json:"defaultAction"` Architectures []Arch `json:"architectures,omitempty"` Syscalls []LinuxSyscall `json:"syscalls"` } // Arch used for additional architectures type Arch string // Additional architectures permitted to be used for system calls // By default only the native architecture of the kernel is permitted const ( ArchX86 Arch = "SCMP_ARCH_X86" ArchX86_64 Arch = "SCMP_ARCH_X86_64" ArchX32 Arch = "SCMP_ARCH_X32" ArchARM Arch = "SCMP_ARCH_ARM" ArchAARCH64 Arch = "SCMP_ARCH_AARCH64" ArchMIPS Arch = "SCMP_ARCH_MIPS" ArchMIPS64 Arch = "SCMP_ARCH_MIPS64" ArchMIPS64N32 Arch = "SCMP_ARCH_MIPS64N32" ArchMIPSEL Arch = "SCMP_ARCH_MIPSEL" ArchMIPSEL64 Arch = "SCMP_ARCH_MIPSEL64" ArchMIPSEL64N32 Arch = "SCMP_ARCH_MIPSEL64N32" ArchPPC Arch = "SCMP_ARCH_PPC" ArchPPC64 Arch = "SCMP_ARCH_PPC64" ArchPPC64LE Arch = "SCMP_ARCH_PPC64LE" ArchS390 Arch = "SCMP_ARCH_S390" ArchS390X Arch = "SCMP_ARCH_S390X" ArchPARISC Arch = "SCMP_ARCH_PARISC" ArchPARISC64 Arch = "SCMP_ARCH_PARISC64" ) // LinuxSeccompAction taken upon Seccomp rule match type LinuxSeccompAction string // Define actions for Seccomp rules const ( ActKill LinuxSeccompAction = "SCMP_ACT_KILL" ActTrap LinuxSeccompAction = "SCMP_ACT_TRAP" ActErrno LinuxSeccompAction = "SCMP_ACT_ERRNO" ActTrace LinuxSeccompAction = "SCMP_ACT_TRACE" ActAllow LinuxSeccompAction = "SCMP_ACT_ALLOW" ) // LinuxSeccompOperator used to match syscall arguments in Seccomp type LinuxSeccompOperator string // Define operators for syscall arguments in Seccomp const ( OpNotEqual LinuxSeccompOperator = "SCMP_CMP_NE" OpLessThan LinuxSeccompOperator = "SCMP_CMP_LT" OpLessEqual LinuxSeccompOperator = "SCMP_CMP_LE" OpEqualTo LinuxSeccompOperator = "SCMP_CMP_EQ" OpGreaterEqual LinuxSeccompOperator = "SCMP_CMP_GE" OpGreaterThan LinuxSeccompOperator = "SCMP_CMP_GT" OpMaskedEqual LinuxSeccompOperator = "SCMP_CMP_MASKED_EQ" ) // LinuxSeccompArg used for matching specific syscall arguments in Seccomp type LinuxSeccompArg struct { Index uint `json:"index"` Value uint64 `json:"value"` ValueTwo uint64 `json:"valueTwo"` Op LinuxSeccompOperator `json:"op"` } // LinuxSyscall is used to match a syscall in Seccomp type LinuxSyscall struct { Names []string `json:"names"` Action LinuxSeccompAction `json:"action"` Args []LinuxSeccompArg `json:"args,omitempty"` } // LinuxIntelRdt has container runtime resource constraints // for Intel RDT/CAT which introduced in Linux 4.10 kernel type LinuxIntelRdt struct { // The schema for L3 cache id and capacity bitmask (CBM) // Format: "L3:=;=;..." L3CacheSchema string `json:"l3CacheSchema,omitempty"` } cadvisor-0.27.1/vendor/github.com/opencontainers/runtime-spec/specs-go/state.go000066400000000000000000000011671315410276000276040ustar00rootroot00000000000000package specs // State holds information about the runtime state of the container. type State struct { // Version is the version of the specification that is supported. Version string `json:"ociVersion"` // ID is the container ID ID string `json:"id"` // Status is the runtime status of the container. Status string `json:"status"` // Pid is the process ID for the container process. Pid int `json:"pid"` // Bundle is the path to the container's bundle directory. Bundle string `json:"bundle"` // Annotations are key values associated with the container. Annotations map[string]string `json:"annotations,omitempty"` } cadvisor-0.27.1/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go000066400000000000000000000010341315410276000301420ustar00rootroot00000000000000package specs import "fmt" const ( // VersionMajor is for an API incompatible changes VersionMajor = 1 // VersionMinor is for functionality in a backwards-compatible manner VersionMinor = 0 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. VersionDev = "-rc5-dev" ) // Version is the specification version that the package types support. var Version = fmt.Sprintf("%d.%d.%d%s", VersionMajor, VersionMinor, VersionPatch, VersionDev) cadvisor-0.27.1/vendor/github.com/pborman/000077500000000000000000000000001315410276000204045ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/pkg/000077500000000000000000000000001315410276000175275ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/prometheus/000077500000000000000000000000001315410276000211415ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/seccomp/000077500000000000000000000000001315410276000203775ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/sirupsen/000077500000000000000000000000001315410276000206165ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/stretchr/000077500000000000000000000000001315410276000206045ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/syndtr/000077500000000000000000000000001315410276000202715ustar00rootroot00000000000000cadvisor-0.27.1/vendor/github.com/vishvananda/000077500000000000000000000000001315410276000212505ustar00rootroot00000000000000cadvisor-0.27.1/vendor/golang.org/000077500000000000000000000000001315410276000167445ustar00rootroot00000000000000cadvisor-0.27.1/vendor/golang.org/x/000077500000000000000000000000001315410276000172135ustar00rootroot00000000000000cadvisor-0.27.1/vendor/google.golang.org/000077500000000000000000000000001315410276000202175ustar00rootroot00000000000000cadvisor-0.27.1/vendor/google.golang.org/api/000077500000000000000000000000001315410276000207705ustar00rootroot00000000000000cadvisor-0.27.1/vendor/google.golang.org/api/AUTHORS000066400000000000000000000005141315410276000220400ustar00rootroot00000000000000# This is the official list of 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. cadvisor-0.27.1/vendor/google.golang.org/api/CONTRIBUTORS000066400000000000000000000036121315410276000226520ustar00rootroot00000000000000# This is the official list of people who can contribute # (and typically have contributed) code to the repository. # The AUTHORS file lists the copyright holders; this file # lists people. For example, Google employees are listed here # but not in AUTHORS, because Google holds the copyright. # # The submission process automatically checks to make sure # that people submitting code are listed in this file (by email address). # # Names should be added to this file only after verifying that # the individual or the individual's organization has agreed to # the appropriate Contributor License Agreement, found here: # # https://cla.developers.google.com/about/google-individual # https://cla.developers.google.com/about/google-corporate # # The agreement for individuals can be filled out on the web. # # When adding J Random Contributor's name to this file, # either J's name or J's organization's name should be # added to the AUTHORS file, depending on whether the # individual or corporate CLA was used. # Names should be added to this file like so: # Name # # An entry with two email addresses specifies that the # first address should be used in the submit logs and # that the second address should be recognized as the # same person when interacting with Rietveld. # Please keep the list sorted. Alain Vongsouvanhalainv Andrew Gerrand Brad Fitzpatrick Dragoslav Mitrinovic Eric Koleda Francesc Campoy Garrick Evans Glenn Lewis Ivan Krasin Jason Hall Johan Euphrosine Kostik Shtoyk Michael McGreevy Nick Craig-Wood Scott Van Woudenberg Takashi Matsuo cadvisor-0.27.1/vendor/google.golang.org/api/LICENSE000066400000000000000000000027031315410276000217770ustar00rootroot00000000000000Copyright (c) 2011 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cadvisor-0.27.1/vendor/gopkg.in/000077500000000000000000000000001315410276000164235ustar00rootroot00000000000000cadvisor-0.27.1/vendor/gopkg.in/olivere/000077500000000000000000000000001315410276000200705ustar00rootroot00000000000000cadvisor-0.27.1/version/000077500000000000000000000000001315410276000150775ustar00rootroot00000000000000cadvisor-0.27.1/version/version.go000066400000000000000000000017721315410276000171220ustar00rootroot00000000000000// 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 version // Build information. Populated at build-time. var ( Version string Revision string Branch string BuildUser string BuildDate string GoVersion string ) // Info provides the iterable version information. var Info = map[string]string{ "version": Version, "revision": Revision, "branch": Branch, "buildUser": BuildUser, "buildDate": BuildDate, "goVersion": GoVersion, } cadvisor-0.27.1/zfs/000077500000000000000000000000001315410276000142145ustar00rootroot00000000000000cadvisor-0.27.1/zfs/watcher.go000066400000000000000000000054341315410276000162060ustar00rootroot00000000000000// Copyright 2016 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 zfs import ( "fmt" "sync" "time" "github.com/golang/glog" zfs "github.com/mistifyio/go-zfs" ) // zfsWatcher maintains a cache of filesystem -> usage stats for a // zfs filesystem type ZfsWatcher struct { filesystem string lock *sync.RWMutex cache map[string]uint64 period time.Duration stopChan chan struct{} } // NewThinPoolWatcher returns a new ThinPoolWatcher for the given devicemapper // thin pool name and metadata device or an error. func NewZfsWatcher(filesystem string) (*ZfsWatcher, error) { return &ZfsWatcher{ filesystem: filesystem, lock: &sync.RWMutex{}, cache: make(map[string]uint64), period: 15 * time.Second, stopChan: make(chan struct{}), }, nil } // Start starts the ZfsWatcher. func (w *ZfsWatcher) Start() { err := w.Refresh() if err != nil { glog.Errorf("encountered error refreshing zfs watcher: %v", err) } for { select { case <-w.stopChan: return case <-time.After(w.period): start := time.Now() err = w.Refresh() if err != nil { glog.Errorf("encountered error refreshing zfs watcher: %v", err) } // print latency for refresh duration := time.Since(start) glog.V(5).Infof("zfs(%d) took %s", start.Unix(), duration) } } } // Stop stops the ZfsWatcher. func (w *ZfsWatcher) Stop() { close(w.stopChan) } // GetUsage gets the cached usage value of the given filesystem. func (w *ZfsWatcher) GetUsage(filesystem string) (uint64, error) { w.lock.RLock() defer w.lock.RUnlock() v, ok := w.cache[filesystem] if !ok { return 0, fmt.Errorf("no cached value for usage of filesystem %v", filesystem) } return v, nil } // Refresh performs a zfs get func (w *ZfsWatcher) Refresh() error { w.lock.Lock() defer w.lock.Unlock() newCache := make(map[string]uint64) parent, err := zfs.GetDataset(w.filesystem) if err != nil { glog.Errorf("encountered error getting zfs filesystem: %s: %v", w.filesystem, err) return err } children, err := parent.Children(0) if err != nil { glog.Errorf("encountered error getting children of zfs filesystem: %s: %v", w.filesystem, err) return err } for _, ds := range children { newCache[ds.Name] = ds.Used } w.cache = newCache return nil }