Update to github.com/google/cadvisor v0.55.1

Signed-off-by: Davanum Srinivas <davanum@gmail.com>
This commit is contained in:
Davanum Srinivas 2025-12-18 08:30:42 -05:00
parent dce2e8cef7
commit 95cf1f264d
No known key found for this signature in database
GPG key ID: 6DEA177048756885
118 changed files with 1802 additions and 3834 deletions

View file

@ -1,29 +0,0 @@
= vendor/github.com/karrick/godirwalk licensed under: =
BSD 2-Clause License
Copyright (c) 2017, Karrick McDermott
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.
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 HOLDER 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.
= vendor/github.com/karrick/godirwalk/LICENSE 7bea66fc0a31c6329f9392034bee75d2

View file

@ -1,204 +0,0 @@
= vendor/github.com/mistifyio/go-zfs licensed under: =
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 (c) 2014, OmniTI Computer Consulting, 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.
= vendor/github.com/mistifyio/go-zfs/LICENSE cce9462224bfb44c1866ef7bd5eddf54

3
go.mod
View file

@ -29,7 +29,7 @@ require (
github.com/go-logr/logr v1.4.3
github.com/go-openapi/jsonreference v0.20.2
github.com/godbus/dbus/v5 v5.2.0
github.com/google/cadvisor v0.53.0
github.com/google/cadvisor v0.55.1
github.com/google/cel-go v0.26.0
github.com/google/gnostic-models v0.7.0
github.com/google/go-cmp v0.7.0
@ -168,7 +168,6 @@ require (
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/karrick/godirwalk v1.17.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.7 // indirect

16
go.sum
View file

@ -94,10 +94,10 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@ -161,8 +161,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cadvisor v0.53.0 h1:pmveUw2VBlr/T2SBE9Fsp8gdLhKWyOBkECGbaas9mcI=
github.com/google/cadvisor v0.53.0/go.mod h1:Tz3zf/exzFfdWd1T/U/9eNst0ZR2C6CIV62LJATj5tg=
github.com/google/cadvisor v0.55.1 h1:81OXN/Hr9RVME6NJw2DI7YKoyR4MkGESrgjKlCAiHBk=
github.com/google/cadvisor v0.55.1/go.mod h1:Zbo4qO/Nyvsy7PfNAcBkXJz2G/VzYytUUc+iNqX8px0=
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
@ -200,8 +200,6 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI=
github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@ -271,7 +269,7 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.3.0/go.mod h1:9wbWt42gV+KRxKRVVugNP6D5+PQciRbenB4fLVsqGPs=
github.com/opencontainers/runc v1.3.3/go.mod h1:D7rL72gfWxVs9cJ2/AayxB0Hlvn9g0gaF1R7uunumSI=
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84=

View file

@ -41,7 +41,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/opencontainers/runc v1.3.0 h1:cvP7xbEvD0QQAs0nZKLzkVog2OPZhI/V2w3WmTmUSXI=
github.com/opencontainers/runc v1.3.3 h1:qlmBbbhu+yY0QM7jqfuat7M1H3/iXjju3VkP9lkFQr4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=

View file

@ -32,6 +32,14 @@ import (
_ "github.com/google/cadvisor/container/crio/install"
_ "github.com/google/cadvisor/container/systemd/install"
// Register filesystem plugins needed for container stats.
_ "github.com/google/cadvisor/fs/btrfs/install"
_ "github.com/google/cadvisor/fs/devicemapper/install"
_ "github.com/google/cadvisor/fs/nfs/install"
_ "github.com/google/cadvisor/fs/overlay/install"
_ "github.com/google/cadvisor/fs/tmpfs/install"
_ "github.com/google/cadvisor/fs/vfs/install"
"github.com/google/cadvisor/cache/memory"
cadvisormetrics "github.com/google/cadvisor/container"
cadvisorapi "github.com/google/cadvisor/info/v1"

View file

@ -29,6 +29,38 @@ import (
// ErrDataNotFound is the error resulting if failed to find a container in memory cache.
var ErrDataNotFound = errors.New("unable to find data in memory cache")
// containerCacheMap is a typed wrapper around sync.Map that eliminates the need
// for type assertions at every call site. It stores container name strings
// mapped to *containerCache values.
type containerCacheMap struct {
m sync.Map
}
// Load retrieves a container cache by name. Returns nil, false if not found.
func (c *containerCacheMap) Load(name string) (*containerCache, bool) {
v, ok := c.m.Load(name)
if !ok {
return nil, false
}
return v.(*containerCache), true
}
// Store saves a container cache with the given name.
func (c *containerCacheMap) Store(name string, cache *containerCache) {
c.m.Store(name, cache)
}
// LoadOrStore returns the existing cache if present, otherwise stores and returns the given one.
func (c *containerCacheMap) LoadOrStore(name string, cache *containerCache) (*containerCache, bool) {
v, loaded := c.m.LoadOrStore(name, cache)
return v.(*containerCache), loaded
}
// Delete removes a container cache by name.
func (c *containerCacheMap) Delete(name string) {
c.m.Delete(name)
}
// TODO(vmarmol): See about refactoring this class, we have an unnecessary redirection of containerCache and InMemoryCache.
// containerCache is used to store per-container information
type containerCache struct {
@ -67,24 +99,18 @@ func newContainerStore(ref info.ContainerReference, maxAge time.Duration) *conta
}
type InMemoryCache struct {
lock sync.RWMutex
containerCacheMap map[string]*containerCache
containerCacheMap containerCacheMap
maxAge time.Duration
backend []storage.StorageDriver
}
func (c *InMemoryCache) AddStats(cInfo *info.ContainerInfo, stats *info.ContainerStats) error {
var cstore *containerCache
var ok bool
func() {
c.lock.Lock()
defer c.lock.Unlock()
if cstore, ok = c.containerCacheMap[cInfo.ContainerReference.Name]; !ok {
cstore = newContainerStore(cInfo.ContainerReference, c.maxAge)
c.containerCacheMap[cInfo.ContainerReference.Name] = cstore
}
}()
name := cInfo.ContainerReference.Name
cstore, ok := c.containerCacheMap.Load(name)
if !ok {
newStore := newContainerStore(cInfo.ContainerReference, c.maxAge)
cstore, _ = c.containerCacheMap.LoadOrStore(name, newStore)
}
for _, backend := range c.backend {
// TODO(monnand): To deal with long delay write operations, we
@ -98,34 +124,20 @@ func (c *InMemoryCache) AddStats(cInfo *info.ContainerInfo, stats *info.Containe
}
func (c *InMemoryCache) RecentStats(name string, start, end time.Time, maxStats int) ([]*info.ContainerStats, error) {
var cstore *containerCache
var ok bool
err := func() error {
c.lock.RLock()
defer c.lock.RUnlock()
if cstore, ok = c.containerCacheMap[name]; !ok {
return ErrDataNotFound
}
return nil
}()
if err != nil {
return nil, err
cstore, ok := c.containerCacheMap.Load(name)
if !ok {
return nil, ErrDataNotFound
}
return cstore.RecentStats(start, end, maxStats)
}
func (c *InMemoryCache) Close() error {
c.lock.Lock()
c.containerCacheMap = make(map[string]*containerCache, 32)
c.lock.Unlock()
c.containerCacheMap = containerCacheMap{}
return nil
}
func (c *InMemoryCache) RemoveContainer(containerName string) error {
c.lock.Lock()
delete(c.containerCacheMap, containerName)
c.lock.Unlock()
c.containerCacheMap.Delete(containerName)
return nil
}
@ -133,10 +145,8 @@ func New(
maxAge time.Duration,
backend []storage.StorageDriver,
) *InMemoryCache {
ret := &InMemoryCache{
containerCacheMap: make(map[string]*containerCache, 32),
maxAge: maxAge,
backend: backend,
return &InMemoryCache{
maxAge: maxAge,
backend: backend,
}
return ret
}

View file

@ -67,7 +67,7 @@ func NewCollector(collectorName string, configFile []byte, metricCountLimit int,
// 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")
return nil, fmt.Errorf("no metrics provided in config")
}
minPollFrequency := time.Duration(0)
@ -83,7 +83,7 @@ func NewCollector(collectorName string, configFile []byte, metricCountLimit int,
regexprs[ind], err = regexp.Compile(metricConfig.Regex)
if err != nil {
return nil, fmt.Errorf("Invalid regexp %v for metric %v", metricConfig.Regex, metricConfig.Name)
return nil, fmt.Errorf("invalid regexp %v for metric %v", metricConfig.Regex, metricConfig.Name)
}
}
@ -94,7 +94,7 @@ func NewCollector(collectorName string, configFile []byte, metricCountLimit int,
}
if len(configInJSON.MetricsConfig) > metricCountLimit {
return nil, fmt.Errorf("Too many metrics defined: %d limit: %d", len(configInJSON.MetricsConfig), metricCountLimit)
return nil, fmt.Errorf("too many metrics defined: %d limit: %d", len(configInJSON.MetricsConfig), metricCountLimit)
}
return &GenericCollector{
@ -173,10 +173,10 @@ func (collector *GenericCollector) Collect(metrics map[string][]v1.MetricVal) (t
}
} else {
errorSlice = append(errorSlice, fmt.Errorf("Unexpected value of 'data_type' for metric '%v' in config ", metricConfig.Name))
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))
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)

View file

@ -72,7 +72,7 @@ func NewPrometheusCollector(collectorName string, configFile []byte, metricCount
}
if metricCountLimit < 0 {
return nil, fmt.Errorf("Metric count limit must be greater than or equal to 0")
return nil, fmt.Errorf("metric count limit must be greater than or equal to 0")
}
var metricsSet map[string]bool
@ -84,7 +84,7 @@ func NewPrometheusCollector(collectorName string, configFile []byte, metricCount
}
if len(configInJSON.MetricsConfig) > metricCountLimit {
return nil, fmt.Errorf("Too many metrics defined: %d limit %d", 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)

View file

@ -16,11 +16,10 @@ 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 == "" {
func (ec *EndpointConfig) configure(containerHandler container.ContainerHandler) {
// If the exact URL was not specified, generate it based on the ip address of the container.
if ec.URL == "" {
ipAddress := containerHandler.GetContainerIPAddress()
endpointConfig.URL = endpoint.URLConfig.Protocol + "://" + ipAddress + ":" + endpoint.URLConfig.Port.String() + endpoint.URLConfig.Path
ec.URL = ec.URLConfig.Protocol + "://" + ipAddress + ":" + ec.URLConfig.Port.String() + ec.URLConfig.Path
}
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// 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

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// Handler for Docker containers.
package common

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package common
import (
@ -25,7 +27,6 @@ import (
"strings"
"time"
"github.com/karrick/godirwalk"
"github.com/opencontainers/cgroups"
"golang.org/x/sys/unix"
@ -301,16 +302,11 @@ func readUInt64(dirpath string, file string) uint64 {
// 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 {
buf := make([]byte, godirwalk.MinimumScratchBufferSize)
return listDirectories(dirpath, parent, recursive, output, buf)
}
func listDirectories(dirpath string, parent string, recursive bool, output map[string]struct{}, buf []byte) error {
dirents, err := godirwalk.ReadDirents(dirpath, buf)
dirents, err := os.ReadDir(dirpath)
if err != nil {
// Ignore if this hierarchy does not exist.
if errors.Is(err, fs.ErrNotExist) {
err = nil
return nil
}
return err
}
@ -326,8 +322,7 @@ func listDirectories(dirpath string, parent string, recursive bool, output map[s
// List subcontainers if asked to.
if recursive {
err := listDirectories(path.Join(dirpath, dirname), name, true, output, buf)
if err != nil {
if err := ListDirectories(path.Join(dirpath, dirname), name, true, output); err != nil {
return err
}
}
@ -387,6 +382,10 @@ func AssignDeviceNamesToDiskStats(namer DeviceNamer, stats *info.DiskIoStats) {
stats.IoTime,
stats.IoWaitTime,
stats.Sectors,
stats.IoCostUsage,
stats.IoCostWait,
stats.IoCostIndebt,
stats.IoCostIndelay,
)
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package common
import (

View file

@ -34,7 +34,6 @@ const (
ContainerTypeDocker
ContainerTypeCrio
ContainerTypeContainerd
ContainerTypeMesos
ContainerTypePodman
)
@ -64,6 +63,11 @@ type ContainerHandler interface {
// Returns the container's ip address, if available
GetContainerIPAddress() string
// GetExitCode returns the container's exit code if available.
// Returns an error if the container has not exited, exit codes are not supported
// for this handler type, or the container information is unavailable.
GetExitCode() (int, error)
// Returns whether the container still exists.
Exists() bool

View file

@ -45,6 +45,8 @@ type client struct {
type ContainerdClient interface {
LoadContainer(ctx context.Context, id string) (*containers.Container, error)
TaskPid(ctx context.Context, id string) (uint32, error)
LoadTaskProcess(ctx context.Context, id string) (*tasktypes.Process, error)
TaskExitStatus(ctx context.Context, id string) (uint32, error)
Version(ctx context.Context) (string, error)
}
@ -54,6 +56,7 @@ var (
var once sync.Once
var ctrdClient ContainerdClient = nil
var ctrdClientErr error = nil
const (
maxBackoffDelay = 3 * time.Second
@ -64,11 +67,10 @@ const (
// Client creates a containerd client
func Client(address, namespace string) (ContainerdClient, error) {
var retErr error
once.Do(func() {
tryConn, err := net.DialTimeout("unix", address, connectionTimeout)
if err != nil {
retErr = fmt.Errorf("containerd: cannot unix dial containerd api service: %v", err)
ctrdClientErr = fmt.Errorf("containerd: cannot unix dial containerd api service: %v", err)
return
}
tryConn.Close()
@ -97,7 +99,7 @@ func Client(address, namespace string) (ContainerdClient, error) {
//nolint:staticcheck // SA1019
conn, err := grpc.DialContext(ctx, dialer.DialAddress(address), gopts...)
if err != nil {
retErr = err
ctrdClientErr = err
return
}
ctrdClient = &client{
@ -106,7 +108,7 @@ func Client(address, namespace string) (ContainerdClient, error) {
versionService: versionapi.NewVersionClient(conn),
}
})
return ctrdClient, retErr
return ctrdClient, ctrdClientErr
}
func (c *client) LoadContainer(ctx context.Context, id string) (*containers.Container, error) {
@ -132,6 +134,30 @@ func (c *client) TaskPid(ctx context.Context, id string) (uint32, error) {
return response.Process.Pid, nil
}
func (c *client) LoadTaskProcess(ctx context.Context, id string) (*tasktypes.Process, error) {
response, err := c.taskService.Get(ctx, &tasksapi.GetRequest{
ContainerID: id,
})
if err != nil {
return nil, errgrpc.ToNative(err)
}
return response.Process, nil
}
func (c *client) TaskExitStatus(ctx context.Context, id string) (uint32, error) {
response, err := c.taskService.Get(ctx, &tasksapi.GetRequest{
ContainerID: id,
})
if err != nil {
return 0, errgrpc.ToNative(err)
}
if response.Process.Status != tasktypes.Status_STOPPED {
return 0, fmt.Errorf("container %s has not exited (status: %v)", id, response.Process.Status)
}
return response.Process.ExitStatus, nil
}
func (c *client) Version(ctx context.Context) (string, error) {
response, err := c.versionService.Version(ctx, &emptypb.Empty{})
if err != nil {

View file

@ -12,16 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package containerd
import (
"context"
"flag"
"fmt"
"path"
"regexp"
"strings"
"golang.org/x/net/context"
"k8s.io/klog/v2"
"github.com/google/cadvisor/container"

View file

@ -16,9 +16,11 @@
package containerd
import (
"github.com/google/cadvisor/container/containerd/namespaces"
"golang.org/x/net/context"
"context"
"google.golang.org/grpc"
"github.com/google/cadvisor/container/containerd/namespaces"
)
type namespaceInterceptor struct {

View file

@ -12,10 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// Handler for containerd containers.
package containerd
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -25,7 +28,6 @@ import (
"github.com/containerd/errdefs"
"github.com/opencontainers/cgroups"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/net/context"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
@ -50,6 +52,7 @@ type containerdContainerHandler struct {
includedMetrics container.MetricSet
libcontainerHandler *containerlibcontainer.Handler
client ContainerdClient
}
var _ container.ContainerHandler = &containerdContainerHandler{}
@ -143,6 +146,7 @@ func newContainerdContainerHandler(
includedMetrics: metrics,
reference: containerReference,
libcontainerHandler: libcontainerHandler,
client: client,
}
// Add the name and bare ID as aliases of the container.
handler.image = cntr.Image
@ -248,3 +252,12 @@ func (h *containerdContainerHandler) GetContainerIPAddress() string {
// containerd doesnt take care of networking.So it doesnt maintain networking states
return ""
}
func (h *containerdContainerHandler) GetExitCode() (int, error) {
ctx := context.Background()
exitStatus, err := h.client.TaskExitStatus(ctx, h.reference.Id)
if err != nil {
return -1, err
}
return int(exitStatus), nil
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// The install package registers containerd.NewPlugin() as the "containerd" container provider when imported
package install

View file

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !windows
// +build !windows
/*
Copyright The containerd Authors.

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package containerd
import (

View file

@ -72,7 +72,7 @@ type crioClientImpl struct {
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
if len(addr) > maxUnixSocketPathSize {
return fmt.Errorf("Unix socket path %q is too long", addr)
return fmt.Errorf("unix socket path %q is too long", addr)
}
// No need for compression in local communications.
tr.DisableCompression = true
@ -149,9 +149,9 @@ func (c *crioClientImpl) ContainerInfo(id string) (*ContainerInfo, error) {
if resp.StatusCode != http.StatusOK {
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Error finding container %s: Status %d", id, resp.StatusCode)
return nil, fmt.Errorf("error finding container %s: status %d", id, resp.StatusCode)
}
return nil, fmt.Errorf("Error finding container %s: Status %d returned error %s", id, resp.StatusCode, string(respBody))
return nil, fmt.Errorf("error finding container %s: status %d returned error %s", id, resp.StatusCode, string(respBody))
}
if err := json.NewDecoder(resp.Body).Decode(&cInfo); err != nil {

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package crio
import (

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// Handler for CRI-O containers.
package crio
@ -360,3 +362,7 @@ func (h *crioContainerHandler) Exists() bool {
func (h *crioContainerHandler) Type() container.ContainerType {
return container.ContainerTypeCrio
}
func (h *crioContainerHandler) GetExitCode() (int, error) {
return -1, fmt.Errorf("exit code not available from CRI-O API")
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// The install package registers crio.NewPlugin() as the "crio" container provider when imported
package install

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package crio
import (

View file

@ -192,7 +192,6 @@ func RegisterPlugin(name string, plugin Plugin) error {
if _, found := plugins[name]; found {
return fmt.Errorf("Plugin %q was registered twice", name)
}
klog.V(4).Infof("Registered Plugin %q", name)
plugins[name] = plugin
return nil
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package libcontainer
import (
@ -311,7 +313,7 @@ func processStatsFromProcs(rootFs string, cgroupPath string, rootPid int) (info.
func (h *Handler) schedulerStatsFromProcs() (info.CpuSchedstat, error) {
pids, err := h.cgroupManager.GetAllPids()
if err != nil {
return info.CpuSchedstat{}, fmt.Errorf("Could not get PIDs for container %d: %w", h.pid, err)
return info.CpuSchedstat{}, fmt.Errorf("could not get PIDs for container %d: %w", h.pid, err)
}
alivePids := make(map[int]struct{}, len(pids))
for _, pid := range pids {
@ -771,6 +773,8 @@ func setCPUStats(s *cgroups.Stats, ret *info.ContainerStats, withPerCPU bool) {
ret.Cpu.CFS.Periods = s.CpuStats.ThrottlingData.Periods
ret.Cpu.CFS.ThrottledPeriods = s.CpuStats.ThrottlingData.ThrottledPeriods
ret.Cpu.CFS.ThrottledTime = s.CpuStats.ThrottlingData.ThrottledTime
ret.Cpu.CFS.BurstsPeriods = s.CpuStats.BurstData.BurstsPeriods
ret.Cpu.CFS.BurstTime = s.CpuStats.BurstData.BurstTime
setPSIStats(s.CpuStats.PSI, &ret.Cpu.PSI)
if !withPerCPU {
@ -793,6 +797,10 @@ func setDiskIoStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.DiskIo.IoWaitTime = diskStatsCopy(s.BlkioStats.IoWaitTimeRecursive)
ret.DiskIo.IoMerged = diskStatsCopy(s.BlkioStats.IoMergedRecursive)
ret.DiskIo.IoTime = diskStatsCopy(s.BlkioStats.IoTimeRecursive)
ret.DiskIo.IoCostUsage = diskStatsCopy(s.BlkioStats.IoCostUsage)
ret.DiskIo.IoCostWait = diskStatsCopy(s.BlkioStats.IoCostWait)
ret.DiskIo.IoCostIndebt = diskStatsCopy(s.BlkioStats.IoCostIndebt)
ret.DiskIo.IoCostIndelay = diskStatsCopy(s.BlkioStats.IoCostIndelay)
setPSIStats(s.BlkioStats.PSI, &ret.DiskIo.PSI)
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package libcontainer
import (

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package raw
import (

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// Handler for "raw" containers.
package raw
@ -281,6 +283,10 @@ func (h *rawContainerHandler) Type() container.ContainerType {
return container.ContainerTypeRaw
}
func (h *rawContainerHandler) GetExitCode() (int, error) {
return -1, fmt.Errorf("exit codes not applicable for raw cgroup containers")
}
type fsNamer struct {
fs []fs.Fs
factory info.MachineInfoFactory

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// Package container defines types for sub-container events and also
// defines an interface for container operation handlers.
package raw

View file

@ -33,7 +33,7 @@ func (f *systemdFactory) String() string {
}
func (f *systemdFactory) NewContainerHandler(name string, metadataEnvAllowList []string, inHostNamespace bool) (container.ContainerHandler, error) {
return nil, fmt.Errorf("Not yet supported")
return nil, fmt.Errorf("not yet supported")
}
func (f *systemdFactory) CanHandleAndAccept(name string) (bool, bool, error) {

View file

@ -58,7 +58,7 @@ func (c *defaultThinLsClient) ThinLs(deviceName string) (map[string]uint64, erro
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 nil, fmt.Errorf("error running command `thin_ls %v`: %v\noutput:\n\n%v", strings.Join(args, " "), err, string(output))
}
return parseThinLsOutput(output), nil

View file

@ -17,19 +17,35 @@ package devicemapper
import (
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"k8s.io/klog/v2"
)
// usageCache is a typed wrapper around atomic.Value that eliminates the need
// for type assertions at every call site. It stores device ID strings mapped
// to usage values (uint64).
type usageCache struct {
v atomic.Value
}
// Load retrieves the current cache map.
func (c *usageCache) Load() map[string]uint64 {
return c.v.Load().(map[string]uint64)
}
// Store saves a new cache map.
func (c *usageCache) Store(m map[string]uint64) {
c.v.Store(m)
}
// 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
cache usageCache
period time.Duration
stopChan chan struct{}
dmsetup DmsetupClient
@ -44,15 +60,16 @@ func NewThinPoolWatcher(poolName, metadataDevice string) (*ThinPoolWatcher, erro
return nil, fmt.Errorf("encountered error creating thin_ls client: %v", err)
}
return &ThinPoolWatcher{poolName: poolName,
w := &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
}
w.cache.Store(map[string]uint64{})
return w, nil
}
// Start starts the ThinPoolWatcher.
@ -87,14 +104,11 @@ func (w *ThinPoolWatcher) Stop() {
// 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]
cache := w.cache.Load()
v, ok := cache[deviceID]
if !ok {
return 0, fmt.Errorf("no cached value for usage of device %v", deviceID)
}
return v, nil
}
@ -106,9 +120,6 @@ const (
// 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)
@ -148,7 +159,7 @@ func (w *ThinPoolWatcher) Refresh() error {
return err
}
w.cache = newCache
w.cache.Store(newCache)
return nil
}

View file

@ -0,0 +1,31 @@
// 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.
//go:build linux
package install
import (
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/btrfs"
"k8s.io/klog/v2"
)
func init() {
err := fs.RegisterPlugin("btrfs", btrfs.NewPlugin())
if err != nil {
klog.Fatalf("Failed to register btrfs fs plugin: %v", err)
}
}

63
vendor/github.com/google/cadvisor/fs/btrfs/mount.go generated vendored Normal file
View file

@ -0,0 +1,63 @@
// 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.
//go:build linux
package btrfs
import (
"fmt"
"syscall"
mount "github.com/moby/sys/mountinfo"
"k8s.io/klog/v2"
)
// major extracts the major device number from a device number.
func major(devNumber uint64) uint {
return uint((devNumber >> 8) & 0xfff)
}
// minor extracts the minor device number from a device number.
func minor(devNumber uint64) uint {
return uint((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00))
}
// GetBtrfsMajorMinorIds gets the major and minor device IDs for a btrfs mount point.
// This is a workaround for 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.
func GetBtrfsMajorMinorIds(mnt *mount.Info) (int, int, error) {
buf := new(syscall.Stat_t)
err := syscall.Stat(mnt.Source, buf)
if err != nil {
err = fmt.Errorf("stat failed on %s with error: %s", mnt.Source, err)
return 0, 0, err
}
klog.V(4).Infof("btrfs mount %#v", mnt)
if buf.Mode&syscall.S_IFMT == syscall.S_IFBLK {
err := syscall.Stat(mnt.Mountpoint, buf)
if err != nil {
err = fmt.Errorf("stat failed on %s with error: %s", mnt.Mountpoint, err)
return 0, 0, err
}
// The type Dev and Rdev in Stat_t are 32bit on mips.
klog.V(4).Infof("btrfs dev major:minor %d:%d\n", int(major(uint64(buf.Dev))), int(minor(uint64(buf.Dev)))) // nolint: unconvert
klog.V(4).Infof("btrfs rdev major:minor %d:%d\n", int(major(uint64(buf.Rdev))), int(minor(uint64(buf.Rdev)))) // nolint: unconvert
return int(major(uint64(buf.Dev))), int(minor(uint64(buf.Dev))), nil // nolint: unconvert
}
return 0, 0, fmt.Errorf("%s is not a block device", mnt.Source)
}

87
vendor/github.com/google/cadvisor/fs/btrfs/plugin.go generated vendored Normal file
View file

@ -0,0 +1,87 @@
// 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.
//go:build linux
package btrfs
import (
"strings"
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/vfs"
mount "github.com/moby/sys/mountinfo"
"k8s.io/klog/v2"
)
type btrfsPlugin struct{}
// NewPlugin creates a new Btrfs filesystem plugin.
func NewPlugin() fs.FsPlugin {
return &btrfsPlugin{}
}
func (p *btrfsPlugin) Name() string {
return "btrfs"
}
// CanHandle returns true if the filesystem type is btrfs.
func (p *btrfsPlugin) CanHandle(fsType string) bool {
return fsType == "btrfs"
}
// Priority returns 100 - Btrfs has higher priority than VFS.
func (p *btrfsPlugin) Priority() int {
return 100
}
// GetStats returns filesystem statistics for Btrfs.
// Btrfs delegates to VFS for stats collection.
func (p *btrfsPlugin) GetStats(device string, partition fs.PartitionInfo) (*fs.FsStats, error) {
// Btrfs uses VFS stats
capacity, free, avail, inodes, inodesFree, err := vfs.GetVfsStats(partition.Mountpoint)
if err != nil {
return nil, err
}
return &fs.FsStats{
Capacity: capacity,
Free: free,
Available: avail,
Inodes: &inodes,
InodesFree: &inodesFree,
Type: fs.VFS,
}, nil
}
// ProcessMount handles Btrfs mount processing.
// 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.
func (p *btrfsPlugin) ProcessMount(mnt *mount.Info) (bool, *mount.Info, error) {
// Only apply fix if Major is 0 and Source starts with /dev/
if mnt.Major == 0 && strings.HasPrefix(mnt.Source, "/dev/") {
major, minor, err := GetBtrfsMajorMinorIds(mnt)
if err != nil {
klog.Warningf("%s", err)
} else {
// Create a copy with corrected values
correctedMnt := *mnt
correctedMnt.Major = major
correctedMnt.Minor = minor
return true, &correctedMnt, nil
}
}
return true, mnt, nil
}

View file

@ -0,0 +1,29 @@
// 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 install
import (
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/devicemapper"
"k8s.io/klog/v2"
)
func init() {
err := fs.RegisterPlugin("devicemapper", devicemapper.NewPlugin())
if err != nil {
klog.Fatalf("Failed to register devicemapper fs plugin: %v", err)
}
}

View file

@ -0,0 +1,66 @@
// 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 devicemapper
import (
"github.com/google/cadvisor/fs"
mount "github.com/moby/sys/mountinfo"
"k8s.io/klog/v2"
)
type dmPlugin struct{}
// NewPlugin creates a new DeviceMapper filesystem plugin.
func NewPlugin() fs.FsPlugin {
return &dmPlugin{}
}
func (p *dmPlugin) Name() string {
return "devicemapper"
}
// CanHandle returns true if the filesystem type is devicemapper.
func (p *dmPlugin) CanHandle(fsType string) bool {
return fsType == "devicemapper"
}
// Priority returns 100 - DeviceMapper has higher priority than VFS.
func (p *dmPlugin) Priority() int {
return 100
}
// GetStats returns filesystem statistics for DeviceMapper thin provisioning.
func (p *dmPlugin) GetStats(device string, partition fs.PartitionInfo) (*fs.FsStats, error) {
capacity, free, avail, err := GetDMStats(device, partition.BlockSize)
if err != nil {
return nil, err
}
klog.V(5).Infof("got devicemapper fs capacity stats: capacity: %v free: %v available: %v", capacity, free, avail)
return &fs.FsStats{
Capacity: capacity,
Free: free,
Available: avail,
Type: fs.DeviceMapper,
}, nil
}
// ProcessMount handles DeviceMapper mount processing.
// For DeviceMapper, no special processing is needed.
func (p *dmPlugin) ProcessMount(mnt *mount.Info) (bool, *mount.Info, error) {
return true, mnt, nil
}

View file

@ -0,0 +1,113 @@
// 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 devicemapper
import (
"fmt"
"os/exec"
"strconv"
"strings"
dm "github.com/google/cadvisor/devicemapper"
)
// GetDMStats returns devicemapper thin provisioning stats.
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
}
// parseDMStatus parses the output of `dmsetup status`.
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
}
// 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
}
// DockerDMDevice returns information about the devicemapper device and "partition" if
// docker is using devicemapper for its storage driver.
func DockerDMDevice(driverStatus map[string]string, dmsetup dm.DmsetupClient) (string, uint, uint, uint, error) {
const driverStatusPoolName = "Pool Name"
poolName, ok := driverStatus[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
}

View file

@ -13,30 +13,25 @@
// limitations under the License.
//go:build linux
// +build linux
// Provides Filesystem Stats
package fs
import (
"bufio"
"context"
"errors"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"syscall"
"time"
zfs "github.com/mistifyio/go-zfs"
mount "github.com/moby/sys/mountinfo"
"github.com/google/cadvisor/devicemapper"
"github.com/google/cadvisor/utils"
"k8s.io/klog/v2"
)
@ -173,28 +168,21 @@ func getFsUUIDToDeviceNameMap() (map[string]string, error) {
func processMounts(mounts []*mount.Info, excludedMountpointPrefixes []string) map[string]partition {
partitions := make(map[string]partition)
supportedFsType := map[string]bool{
// all ext and nfs systems are checked through prefix
// because there are a number of families (e.g., ext3, ext4, nfs3, nfs4...)
"btrfs": true,
"overlay": true,
"tmpfs": true,
"xfs": true,
"zfs": true,
}
for _, mnt := range mounts {
if !strings.HasPrefix(mnt.FSType, "ext") && !strings.HasPrefix(mnt.FSType, "nfs") &&
!supportedFsType[mnt.FSType] {
// Use plugin system to determine if filesystem is supported
plugin := GetPluginForFsType(mnt.FSType)
if plugin == nil {
continue
}
// Avoid bind mounts, exclude tmpfs.
// Avoid bind mounts, but allow tmpfs duplicates (handled by plugin's ProcessMount)
if _, ok := partitions[mnt.Source]; ok {
if mnt.FSType != "tmpfs" {
continue
}
}
// Check for excluded mountpoint prefixes
hasPrefix := false
for _, prefix := range excludedMountpointPrefixes {
if strings.HasPrefix(mnt.Mountpoint, prefix) {
@ -206,32 +194,21 @@ func processMounts(mounts []*mount.Info, excludedMountpointPrefixes []string) ma
continue
}
// using mountpoint to replace device once fstype it tmpfs
if mnt.FSType == "tmpfs" {
mnt.Source = mnt.Mountpoint
// Let plugin process the mount (handles filesystem-specific modifications)
include, processedMnt, err := plugin.ProcessMount(mnt)
if err != nil {
klog.Warningf("error processing mount for %s: %v", mnt.FSType, err)
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 mnt.FSType == "btrfs" && mnt.Major == 0 && strings.HasPrefix(mnt.Source, "/dev/") {
major, minor, err := getBtrfsMajorMinorIds(mnt)
if err != nil {
klog.Warningf("%s", err)
} else {
mnt.Major = major
mnt.Minor = minor
}
if !include {
continue
}
// overlay fix: Making mount source unique for all overlay mounts, using the mount's major and minor ids.
if mnt.FSType == "overlay" {
mnt.Source = fmt.Sprintf("%s_%d-%d", mnt.Source, mnt.Major, mnt.Minor)
}
partitions[mnt.Source] = partition{
fsType: mnt.FSType,
mountpoint: mnt.Mountpoint,
major: uint(mnt.Major),
minor: uint(mnt.Minor),
partitions[processedMnt.Source] = partition{
fsType: processedMnt.FSType,
mountpoint: processedMnt.Mountpoint,
major: uint(processedMnt.Major),
minor: uint(processedMnt.Minor),
}
}
@ -412,81 +389,112 @@ func (i *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error
if err != nil {
return nil, err
}
nfsInfo := make(map[string]Fs, 0)
// statsCache stores cached filesystem stats by cache key for plugins that implement FsCachingPlugin
statsCache := make(map[string]Fs)
for device, partition := range i.partitions {
_, hasMount := mountSet[partition.mountpoint]
_, hasDevice := deviceSet[device]
if mountSet == nil || (hasMount && !hasDevice) {
var (
err error
fs Fs
statsErr error
fs Fs
)
fsType := partition.fsType
if strings.HasPrefix(partition.fsType, "nfs") {
fsType = "nfs"
}
switch fsType {
case DeviceMapper.String():
fs.Capacity, fs.Free, fs.Available, err = getDMStats(device, partition.blockSize)
klog.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():
if _, devzfs := os.Stat("/dev/zfs"); os.IsExist(devzfs) {
fs.Capacity, fs.Free, fs.Available, err = getZfstats(device)
fs.Type = ZFS
break
}
// if /dev/zfs is not present default to VFS
fallthrough
case NFS.String():
devId := fmt.Sprintf("%d:%d", partition.major, partition.minor)
if v, ok := nfsInfo[devId]; ok {
fs = v
break
}
var inodes, inodesFree uint64
fs.Capacity, fs.Free, fs.Available, inodes, inodesFree, err = getVfsStats(partition.mountpoint)
if err != nil {
klog.V(4).Infof("the file system type is %s, partition mountpoint does not exist: %v, error: %v", partition.fsType, partition.mountpoint, err)
break
}
fs.Inodes = &inodes
fs.InodesFree = &inodesFree
fs.Type = VFS
nfsInfo[devId] = fs
default:
var inodes, inodesFree uint64
if utils.FileExists(partition.mountpoint) {
fs.Capacity, fs.Free, fs.Available, inodes, inodesFree, err = getVfsStats(partition.mountpoint)
fs.Inodes = &inodes
fs.InodesFree = &inodesFree
fs.Type = VFS
} else {
klog.V(4).Infof("unable to determine file system type, partition mountpoint does not exist: %v", partition.mountpoint)
}
}
if err != nil {
klog.V(4).Infof("Stat fs failed. Error: %v", err)
} else {
deviceSet[device] = struct{}{}
fs.DeviceInfo = DeviceInfo{
Device: device,
Major: uint(partition.major),
Minor: uint(partition.minor),
}
if val, ok := diskStatsMap[device]; ok {
fs.DiskStats = val
} else {
for k, v := range diskStatsMap {
if v.MajorNum == uint64(partition.major) && v.MinorNum == uint64(partition.minor) {
fs.DiskStats = diskStatsMap[k]
break
// Use plugin system to get filesystem stats
plugin := GetPluginForFsType(partition.fsType)
if plugin == nil {
klog.V(4).Infof("no plugin found for filesystem type: %v", partition.fsType)
continue
}
partInfo := PartitionInfo{
Mountpoint: partition.mountpoint,
Major: partition.major,
Minor: partition.minor,
FsType: partition.fsType,
BlockSize: partition.blockSize,
}
// Check if plugin supports caching and if we have a cached value
var cacheKey string
if cachingPlugin, ok := plugin.(FsCachingPlugin); ok {
cacheKey = cachingPlugin.CacheKey(partInfo)
if cacheKey != "" {
if cachedFs, found := statsCache[cacheKey]; found {
fs = cachedFs
// Skip stats fetching, use cached value
deviceSet[device] = struct{}{}
fs.DeviceInfo = DeviceInfo{
Device: device,
Major: uint(partition.major),
Minor: uint(partition.minor),
}
if val, ok := diskStatsMap[device]; ok {
fs.DiskStats = val
} else {
for k, v := range diskStatsMap {
if v.MajorNum == uint64(partition.major) && v.MinorNum == uint64(partition.minor) {
fs.DiskStats = diskStatsMap[k]
break
}
}
}
filesystems = append(filesystems, fs)
continue
}
}
filesystems = append(filesystems, fs)
}
stats, statsErr := plugin.GetStats(device, partInfo)
if statsErr != nil {
// Handle fallback to VFS for plugins that request it
if errors.Is(statsErr, ErrFallbackToVFS) {
vfsPlugin := GetPluginForFsType("ext4") // VFS handles ext*
if vfsPlugin != nil {
stats, statsErr = vfsPlugin.GetStats(device, partInfo)
}
}
if statsErr != nil {
klog.V(4).Infof("Stat fs failed for %s. Error: %v", partition.fsType, statsErr)
continue
}
}
if stats == nil {
klog.V(4).Infof("no stats returned for %s at %s", partition.fsType, partition.mountpoint)
continue
}
fs.Capacity = stats.Capacity
fs.Free = stats.Free
fs.Available = stats.Available
fs.Inodes = stats.Inodes
fs.InodesFree = stats.InodesFree
fs.Type = stats.Type
// Store in cache if plugin supports caching
if cacheKey != "" {
statsCache[cacheKey] = fs
}
deviceSet[device] = struct{}{}
fs.DeviceInfo = DeviceInfo{
Device: device,
Major: uint(partition.major),
Minor: uint(partition.minor),
}
if val, ok := diskStatsMap[device]; ok {
fs.DiskStats = val
} else {
for k, v := range diskStatsMap {
if v.MajorNum == uint64(partition.major) && v.MinorNum == uint64(partition.minor) {
fs.DiskStats = diskStatsMap[k]
break
}
}
}
filesystems = append(filesystems, fs)
}
}
return filesystems, nil
@ -717,50 +725,12 @@ func (i *RealFsInfo) GetDirUsage(dir string) (UsageInfo, error) {
return GetDirUsage(dir)
}
func getVfsStats(path string) (total uint64, free uint64, avail uint64, inodes uint64, inodesFree uint64, err error) {
// timeout the context with, default is 2sec
timeout := 2
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
type result struct {
total uint64
free uint64
avail uint64
inodes uint64
inodesFree uint64
err error
}
resultChan := make(chan result, 1)
go func() {
var s syscall.Statfs_t
if err = syscall.Statfs(path, &s); err != nil {
total, free, avail, inodes, inodesFree = 0, 0, 0, 0, 0
}
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)
resultChan <- result{total: total, free: free, avail: avail, inodes: inodes, inodesFree: inodesFree, err: err}
}()
select {
case <-ctx.Done():
return 0, 0, 0, 0, 0, ctx.Err()
case res := <-resultChan:
return res.total, res.free, res.avail, res.inodes, res.inodesFree, res.err
}
}
// 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[DriverStatusPoolName]
if !ok || len(poolName) == 0 {
return "", 0, 0, 0, fmt.Errorf("Could not get dm pool name")
return "", 0, 0, 0, fmt.Errorf("could not get dm pool name")
}
out, err := dmsetup.Table(poolName)
@ -783,7 +753,7 @@ func parseDMTable(dmTable string) (uint, uint, uint, error) {
dmFields := strings.Fields(dmTable)
if len(dmFields) < 8 {
return 0, 0, 0, fmt.Errorf("Invalid dmsetup status output: %s", dmTable)
return 0, 0, 0, fmt.Errorf("invalid dmsetup status output: %s", dmTable)
}
major, err := strconv.ParseUint(dmFields[5], 10, 32)
@ -802,56 +772,6 @@ func parseDMTable(dmTable string) (uint, uint, uint, error) {
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
}
// 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.

View file

@ -0,0 +1,31 @@
// 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.
//go:build linux
package install
import (
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/nfs"
"k8s.io/klog/v2"
)
func init() {
err := fs.RegisterPlugin("nfs", nfs.NewPlugin())
if err != nil {
klog.Fatalf("Failed to register nfs fs plugin: %v", err)
}
}

85
vendor/github.com/google/cadvisor/fs/nfs/plugin.go generated vendored Normal file
View file

@ -0,0 +1,85 @@
// 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.
//go:build linux
package nfs
import (
"fmt"
"strings"
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/vfs"
mount "github.com/moby/sys/mountinfo"
"k8s.io/klog/v2"
)
type nfsPlugin struct{}
// Ensure nfsPlugin implements FsCachingPlugin
var _ fs.FsCachingPlugin = &nfsPlugin{}
// NewPlugin creates a new NFS filesystem plugin.
func NewPlugin() fs.FsPlugin {
return &nfsPlugin{}
}
func (p *nfsPlugin) Name() string {
return "nfs"
}
// CanHandle returns true if the filesystem type is NFS (nfs, nfs3, nfs4, etc.).
func (p *nfsPlugin) CanHandle(fsType string) bool {
return strings.HasPrefix(fsType, "nfs")
}
// Priority returns 50 - NFS has medium priority (higher than VFS but lower than specific plugins).
func (p *nfsPlugin) Priority() int {
return 50
}
// GetStats returns filesystem statistics for NFS.
// NFS uses VFS stats.
func (p *nfsPlugin) GetStats(device string, partition fs.PartitionInfo) (*fs.FsStats, error) {
capacity, free, avail, inodes, inodesFree, err := vfs.GetVfsStats(partition.Mountpoint)
if err != nil {
klog.V(4).Infof("the file system type is %s, partition mountpoint does not exist: %v, error: %v",
partition.FsType, partition.Mountpoint, err)
return nil, err
}
return &fs.FsStats{
Capacity: capacity,
Free: free,
Available: avail,
Inodes: &inodes,
InodesFree: &inodesFree,
Type: fs.VFS,
}, nil
}
// ProcessMount handles NFS mount processing.
// For NFS, no special processing is needed.
func (p *nfsPlugin) ProcessMount(mnt *mount.Info) (bool, *mount.Info, error) {
return true, mnt, nil
}
// CacheKey returns a cache key based on device ID (major:minor).
// NFS mounts with the same device ID share the same underlying filesystem,
// so we can cache stats to avoid redundant statfs calls.
func (p *nfsPlugin) CacheKey(partition fs.PartitionInfo) string {
return fmt.Sprintf("%d:%d", partition.Major, partition.Minor)
}

View file

@ -0,0 +1,31 @@
// 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.
//go:build linux
package install
import (
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/overlay"
"k8s.io/klog/v2"
)
func init() {
err := fs.RegisterPlugin("overlay", overlay.NewPlugin())
if err != nil {
klog.Fatalf("Failed to register overlay fs plugin: %v", err)
}
}

76
vendor/github.com/google/cadvisor/fs/overlay/plugin.go generated vendored Normal file
View file

@ -0,0 +1,76 @@
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package overlay
import (
"fmt"
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/vfs"
mount "github.com/moby/sys/mountinfo"
)
type overlayPlugin struct{}
// NewPlugin creates a new Overlay filesystem plugin.
func NewPlugin() fs.FsPlugin {
return &overlayPlugin{}
}
func (p *overlayPlugin) Name() string {
return "overlay"
}
// CanHandle returns true if the filesystem type is overlay or overlay2.
func (p *overlayPlugin) CanHandle(fsType string) bool {
return fsType == "overlay"
}
// Priority returns 100 - Overlay has higher priority than VFS.
func (p *overlayPlugin) Priority() int {
return 100
}
// GetStats returns filesystem statistics for Overlay.
// Overlay delegates to VFS for stats collection.
func (p *overlayPlugin) GetStats(device string, partition fs.PartitionInfo) (*fs.FsStats, error) {
// Overlay uses VFS stats
capacity, free, avail, inodes, inodesFree, err := vfs.GetVfsStats(partition.Mountpoint)
if err != nil {
return nil, err
}
return &fs.FsStats{
Capacity: capacity,
Free: free,
Available: avail,
Inodes: &inodes,
InodesFree: &inodesFree,
Type: fs.VFS,
}, nil
}
// ProcessMount handles Overlay mount processing.
// Overlay fix: Making mount source unique for all overlay mounts, using the mount's major and minor ids.
// This is needed because multiple overlay mounts can have the same source.
func (p *overlayPlugin) ProcessMount(mnt *mount.Info) (bool, *mount.Info, error) {
// Create a copy with unique source
correctedMnt := *mnt
correctedMnt.Source = fmt.Sprintf("%s_%d-%d", mnt.Source, mnt.Major, mnt.Minor)
return true, &correctedMnt, nil
}

179
vendor/github.com/google/cadvisor/fs/plugin.go generated vendored Normal file
View file

@ -0,0 +1,179 @@
// 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"
"fmt"
"sync"
mount "github.com/moby/sys/mountinfo"
"k8s.io/klog/v2"
)
// FsPlugin provides filesystem-specific statistics collection.
type FsPlugin interface {
// Name returns the plugin identifier (e.g., "zfs", "devicemapper", "vfs").
Name() string
// CanHandle returns true if this plugin handles the given filesystem type.
CanHandle(fsType string) bool
// Priority returns the plugin priority (higher = checked first).
// Allows specific plugins (zfs, btrfs) to override generic (vfs).
Priority() int
// GetStats returns filesystem statistics for a partition.
GetStats(device string, partition PartitionInfo) (*FsStats, error)
// ProcessMount optionally modifies mount info during processing.
// Returns (shouldInclude bool, modifiedMount *mount.Info, error).
ProcessMount(mnt *mount.Info) (bool, *mount.Info, error)
}
// FsCachingPlugin is an optional interface for plugins that want to cache
// stats by a key (e.g., device ID) to avoid redundant stat calls.
// This is useful for network filesystems like NFS where multiple mounts
// may point to the same underlying device.
type FsCachingPlugin interface {
FsPlugin
// CacheKey returns a cache key for the given partition.
// Stats will be cached by this key and reused for partitions with the same key.
// Return empty string to disable caching for a specific partition.
CacheKey(partition PartitionInfo) string
}
// FsWatcherPlugin is an optional interface for plugins that provide
// background monitoring (e.g., ZFS watcher, ThinPool watcher).
type FsWatcherPlugin interface {
FsPlugin
// StartWatcher starts background monitoring.
// Returns a Watcher that can be used to get container-level usage.
StartWatcher() (FsWatcher, error)
}
// FsWatcher provides container-level filesystem usage from background monitoring.
type FsWatcher interface {
// GetUsage returns filesystem usage for a specific container/path.
GetUsage(containerID string, deviceID string) (uint64, error)
// Stop stops the background monitoring.
Stop()
}
// PartitionInfo contains information needed for stats collection.
type PartitionInfo struct {
Mountpoint string
Major uint
Minor uint
FsType string
BlockSize uint
}
// FsStats contains filesystem statistics returned by plugins.
type FsStats struct {
Capacity uint64
Free uint64
Available uint64
Inodes *uint64
InodesFree *uint64
Type FsType
}
// ErrFallbackToVFS signals that a specialized plugin cannot handle
// this filesystem and VFS should be used instead.
var ErrFallbackToVFS = errors.New("fallback to VFS")
// Plugin registry (init-time registration only).
var (
pluginsLock sync.RWMutex
plugins = make(map[string]FsPlugin)
)
// RegisterPlugin registers a filesystem plugin.
// This should be called from init() functions.
func RegisterPlugin(name string, plugin FsPlugin) error {
pluginsLock.Lock()
defer pluginsLock.Unlock()
if _, found := plugins[name]; found {
return fmt.Errorf("FsPlugin %q was registered twice", name)
}
klog.V(4).Infof("Registered FsPlugin %q", name)
plugins[name] = plugin
return nil
}
// GetPluginForFsType returns the appropriate plugin for the filesystem type.
// Returns nil if no plugin can handle the filesystem type.
func GetPluginForFsType(fsType string) FsPlugin {
pluginsLock.RLock()
defer pluginsLock.RUnlock()
var best FsPlugin
for _, p := range plugins {
if p.CanHandle(fsType) {
if best == nil || p.Priority() > best.Priority() {
best = p
}
}
}
return best
}
// GetAllPlugins returns all registered plugins.
func GetAllPlugins() []FsPlugin {
pluginsLock.RLock()
defer pluginsLock.RUnlock()
result := make([]FsPlugin, 0, len(plugins))
for _, p := range plugins {
result = append(result, p)
}
return result
}
// InitializeWatchers starts all plugin watchers and returns them.
func InitializeWatchers() map[string]FsWatcher {
pluginsLock.RLock()
defer pluginsLock.RUnlock()
watchers := make(map[string]FsWatcher)
for name, plugin := range plugins {
if wp, ok := plugin.(FsWatcherPlugin); ok {
watcher, err := wp.StartWatcher()
if err != nil {
klog.V(4).Infof("Failed to start watcher for plugin %s: %v", name, err)
continue
}
if watcher != nil {
watchers[name] = watcher
klog.V(4).Infof("Started watcher for FsPlugin %q", name)
}
}
}
return watchers
}
// StopWatchers stops all provided watchers.
func StopWatchers(watchers map[string]FsWatcher) {
for name, watcher := range watchers {
if watcher != nil {
watcher.Stop()
klog.V(4).Infof("Stopped watcher for FsPlugin %q", name)
}
}
}

View file

@ -0,0 +1,31 @@
// 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.
//go:build linux
package install
import (
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/tmpfs"
"k8s.io/klog/v2"
)
func init() {
err := fs.RegisterPlugin("tmpfs", tmpfs.NewPlugin())
if err != nil {
klog.Fatalf("Failed to register tmpfs fs plugin: %v", err)
}
}

80
vendor/github.com/google/cadvisor/fs/tmpfs/plugin.go generated vendored Normal file
View file

@ -0,0 +1,80 @@
// 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.
//go:build linux
package tmpfs
import (
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/vfs"
mount "github.com/moby/sys/mountinfo"
)
type tmpfsPlugin struct{}
// NewPlugin creates a new tmpfs filesystem plugin.
func NewPlugin() fs.FsPlugin {
return &tmpfsPlugin{}
}
func (p *tmpfsPlugin) Name() string {
return "tmpfs"
}
// CanHandle returns true if the filesystem type is tmpfs.
func (p *tmpfsPlugin) CanHandle(fsType string) bool {
return fsType == "tmpfs"
}
// Priority returns 100 - tmpfs has higher priority than VFS.
func (p *tmpfsPlugin) Priority() int {
return 100
}
// GetStats returns filesystem statistics for tmpfs.
// tmpfs delegates to VFS for stats collection.
func (p *tmpfsPlugin) GetStats(device string, partition fs.PartitionInfo) (*fs.FsStats, error) {
// tmpfs uses VFS stats
capacity, free, avail, inodes, inodesFree, err := vfs.GetVfsStats(partition.Mountpoint)
if err != nil {
return nil, err
}
return &fs.FsStats{
Capacity: capacity,
Free: free,
Available: avail,
Inodes: &inodes,
InodesFree: &inodesFree,
Type: fs.VFS,
}, nil
}
// ProcessMount handles tmpfs mount processing.
// For tmpfs, we use the mountpoint as the source to make each mount unique.
// This allows multiple tmpfs mounts with the same "tmpfs" source to coexist.
func (p *tmpfsPlugin) ProcessMount(mnt *mount.Info) (bool, *mount.Info, error) {
// Use mountpoint as source to make each tmpfs mount unique
correctedMnt := *mnt
correctedMnt.Source = mnt.Mountpoint
return true, &correctedMnt, nil
}
// AllowDuplicateSource returns true for tmpfs since multiple tmpfs mounts
// should be tracked separately even if they appear to have the same source.
func (p *tmpfsPlugin) AllowDuplicateSource() bool {
return true
}

View file

@ -0,0 +1,31 @@
// 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.
//go:build linux
package install
import (
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/fs/vfs"
"k8s.io/klog/v2"
)
func init() {
err := fs.RegisterPlugin("vfs", vfs.NewPlugin())
if err != nil {
klog.Fatalf("Failed to register vfs fs plugin: %v", err)
}
}

97
vendor/github.com/google/cadvisor/fs/vfs/plugin.go generated vendored Normal file
View file

@ -0,0 +1,97 @@
// 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.
//go:build linux
package vfs
import (
"strings"
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/utils"
mount "github.com/moby/sys/mountinfo"
"k8s.io/klog/v2"
)
type vfsPlugin struct{}
// NewPlugin creates a new VFS filesystem plugin.
func NewPlugin() fs.FsPlugin {
return &vfsPlugin{}
}
func (p *vfsPlugin) Name() string {
return "vfs"
}
// CanHandle returns true for standard filesystems that use VFS stats.
// This includes ext2/3/4, xfs, and similar block-based filesystems.
// Virtual/pseudo filesystems (proc, sysfs, cgroup, etc.) are excluded.
func (p *vfsPlugin) CanHandle(fsType string) bool {
// Exclude virtual/pseudo filesystems that don't have real disk backing
switch fsType {
case "cgroup", "cgroup2", "cpuset", "mqueue", "proc", "sysfs",
"devtmpfs", "devpts", "securityfs", "debugfs", "tracefs",
"pstore", "configfs", "fusectl", "hugetlbfs", "autofs",
"binfmt_misc", "efivarfs", "rpc_pipefs", "nsfs":
return false
}
// VFS can handle most standard Linux filesystems
if strings.HasPrefix(fsType, "ext") {
return true
}
switch fsType {
case "xfs", "squashfs", "f2fs", "jfs", "reiserfs", "hfs", "hfsplus",
"ntfs", "vfat", "fat", "msdos", "exfat", "udf", "iso9660":
return true
}
// Don't act as a general fallback - only handle known filesystem types
return false
}
// Priority returns 0 - VFS is the lowest priority fallback plugin.
func (p *vfsPlugin) Priority() int {
return 0
}
// GetStats returns filesystem statistics using the statfs syscall.
func (p *vfsPlugin) GetStats(device string, partition fs.PartitionInfo) (*fs.FsStats, error) {
if !utils.FileExists(partition.Mountpoint) {
klog.V(4).Infof("VFS: mountpoint does not exist: %v", partition.Mountpoint)
return nil, nil
}
capacity, free, avail, inodes, inodesFree, err := GetVfsStats(partition.Mountpoint)
if err != nil {
return nil, err
}
return &fs.FsStats{
Capacity: capacity,
Free: free,
Available: avail,
Inodes: &inodes,
InodesFree: &inodesFree,
Type: fs.VFS,
}, nil
}
// ProcessMount handles standard mount processing.
// For VFS, no special processing is needed.
func (p *vfsPlugin) ProcessMount(mnt *mount.Info) (bool, *mount.Info, error) {
return true, mnt, nil
}

63
vendor/github.com/google/cadvisor/fs/vfs/stats.go generated vendored Normal file
View file

@ -0,0 +1,63 @@
// 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.
//go:build linux
package vfs
import (
"context"
"syscall"
"time"
)
// GetVfsStats returns filesystem statistics using the statfs syscall.
// It has a timeout to prevent hanging on unresponsive filesystems.
func GetVfsStats(path string) (total uint64, free uint64, avail uint64, inodes uint64, inodesFree uint64, err error) {
// timeout the context with, default is 2sec
timeout := 2
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
type result struct {
total uint64
free uint64
avail uint64
inodes uint64
inodesFree uint64
err error
}
resultChan := make(chan result, 1)
go func() {
var s syscall.Statfs_t
if err = syscall.Statfs(path, &s); err != nil {
total, free, avail, inodes, inodesFree = 0, 0, 0, 0, 0
}
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)
resultChan <- result{total: total, free: free, avail: avail, inodes: inodes, inodesFree: inodesFree, err: err}
}()
select {
case <-ctx.Done():
return 0, 0, 0, 0, 0, ctx.Err()
case res := <-resultChan:
return res.total, res.free, res.avail, res.inodes, res.inodesFree, res.err
}
}

View file

@ -329,6 +329,13 @@ type CpuCFS struct {
// Total time duration for which tasks in the cgroup have been throttled.
// Unit: nanoseconds.
ThrottledTime uint64 `json:"throttled_time"`
// Total number of periods when CPU burst occurs.
BurstsPeriods uint64 `json:"bursts_periods"`
// Total time duration when CPU burst occurs.
// Unit: nanoseconds.
BurstTime uint64 `json:"burst_time"`
}
// Cpu Aggregated scheduler statistics
@ -374,6 +381,10 @@ type DiskIoStats struct {
IoWaitTime []PerDiskStats `json:"io_wait_time,omitempty"`
IoMerged []PerDiskStats `json:"io_merged,omitempty"`
IoTime []PerDiskStats `json:"io_time,omitempty"`
IoCostUsage []PerDiskStats `json:"io_cost_usage,omitempty"`
IoCostWait []PerDiskStats `json:"io_cost_wait,omitempty"`
IoCostIndebt []PerDiskStats `json:"io_cost_indebt,omitempty"`
IoCostIndelay []PerDiskStats `json:"io_cost_indelay,omitempty"`
PSI PSIStats `json:"psi"`
}
@ -964,6 +975,11 @@ type ProcessStats struct {
Ulimits []UlimitSpec `json:"ulimits,omitempty"`
}
type Health struct {
// Health status of the container
Status string `json:"status"`
}
type ContainerStats struct {
// The time of this stat point.
Timestamp time.Time `json:"timestamp"`
@ -1003,6 +1019,8 @@ type ContainerStats struct {
CpuSet CPUSetStats `json:"cpuset,omitempty"`
OOMEvents uint64 `json:"oom_events,omitempty"`
Health Health `json:"health,omitempty"`
}
func timeEq(t1, t2 time.Time, tolerance time.Duration) bool {
@ -1097,6 +1115,9 @@ const (
type EventData struct {
// Information about an OOM kill event.
OomKill *OomKillEventData `json:"oom,omitempty"`
// Information about a container deletion event.
ContainerDeletion *ContainerDeletionEventData `json:"container_deletion,omitempty"`
}
// Information related to an OOM kill instance
@ -1107,3 +1128,10 @@ type OomKillEventData struct {
// The name of the killed process
ProcessName string `json:"process_name"`
}
// Information related to a container deletion event
type ContainerDeletionEventData struct {
// ExitCode is the exit code of the container.
// A value of -1 indicates the exit code was not available or not applicable.
ExitCode int `json:"exit_code"`
}

View file

@ -200,6 +200,8 @@ type Percentiles struct {
Ninety uint64 `json:"ninety"`
// 95th percentile over the collected sample.
NinetyFive uint64 `json:"ninetyfive"`
// Number of samples used to calculate these percentiles.
Count uint64 `json:"count"`
}
type Usage struct {

View file

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package machine
import (
"bytes"
"flag"
"os"
"path/filepath"
@ -169,10 +170,8 @@ func ContainerOsVersion() string {
func KernelVersion() string {
uname := &unix.Utsname{}
if err := unix.Uname(uname); err != nil {
return "Unknown"
}
return string(uname.Release[:bytes.IndexByte(uname.Release[:], 0)])
return unix.ByteSliceToString(uname.Release[:])
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// The machine package contains functions that extract machine-level specs.
package machine
@ -257,7 +259,7 @@ func getMachineArch() string {
klog.Errorf("Cannot get machine architecture, err: %v", err)
return ""
}
return string(uname.Machine[:])
return unix.ByteSliceToString(uname.Machine[:])
}
// arm32 changes

View file

@ -13,17 +13,17 @@
// limitations under the License.
//go:build freebsd || darwin || linux
// +build freebsd darwin linux
package machine
import (
"fmt"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
"golang.org/x/sys/unix"
)
var rex = regexp.MustCompile("(PRETTY_NAME)=(.*)")
@ -31,12 +31,12 @@ var rex = regexp.MustCompile("(PRETTY_NAME)=(.*)")
// getOperatingSystem gets the name of the current operating system.
func getOperatingSystem() (string, error) {
if runtime.GOOS == "darwin" || runtime.GOOS == "freebsd" {
cmd := exec.Command("uname", "-s")
osName, err := cmd.Output()
uname := unix.Utsname{}
err := unix.Uname(&uname)
if err != nil {
return "", err
}
return string(osName), nil
return unix.ByteSliceToString(uname.Sysname[:]), nil
}
bytes, err := os.ReadFile("/etc/os-release")
if err != nil && os.IsNotExist(err) {

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package manager
import (
@ -64,6 +66,18 @@ type containerInfo struct {
Spec info.ContainerSpec
}
// atomicTime is a lock-free wrapper for storing and retrieving time values.
// It stores time as Unix nanoseconds in an atomic.Int64, enabling concurrent
// reads and writes without mutex contention.
type atomicTime struct {
atomic.Int64
}
// Time returns the stored time value as a time.Time.
func (t *atomicTime) Time() time.Time {
return time.Unix(0, t.Load())
}
type containerData struct {
oomEvents uint64
handler container.ContainerHandler
@ -77,8 +91,8 @@ type containerData struct {
housekeepingInterval time.Duration
maxHousekeepingInterval time.Duration
allowDynamicHousekeeping bool
infoLastUpdatedTime time.Time
statsLastUpdatedTime time.Time
infoLastUpdatedTime atomicTime // Unix nano
statsLastUpdatedTime atomicTime // Unix nano
lastErrorTime time.Time
// used to track time
clock clock.Clock
@ -90,7 +104,8 @@ type containerData struct {
logUsage bool
// Tells the container to stop.
stop chan struct{}
stop chan struct{}
stopOnce sync.Once
// Tells the container to immediately collect stats
onDemandChan chan chan struct{}
@ -126,7 +141,12 @@ func (cd *containerData) Stop() error {
if err != nil {
return err
}
close(cd.stop)
// Use sync.Once to ensure the channel is only closed once, preventing
// panic from concurrent calls to Stop() when multiple goroutines try
// to destroy the same container simultaneously.
cd.stopOnce.Do(func() {
close(cd.stop)
})
cd.perfCollector.Destroy()
cd.resctrlCollector.Destroy()
return nil
@ -145,9 +165,7 @@ func (cd *containerData) allowErrorLogging() bool {
// periodic housekeeping to reset. This should be used sparingly, as calling OnDemandHousekeeping frequently
// can have serious performance costs.
func (cd *containerData) OnDemandHousekeeping(maxAge time.Duration) {
cd.lock.Lock()
timeSinceStatsLastUpdate := cd.clock.Since(cd.statsLastUpdatedTime)
cd.lock.Unlock()
timeSinceStatsLastUpdate := cd.clock.Since(cd.statsLastUpdatedTime.Time())
if timeSinceStatsLastUpdate > maxAge {
housekeepingFinishedChan := make(chan struct{})
cd.onDemandChan <- housekeepingFinishedChan
@ -172,7 +190,7 @@ func (cd *containerData) notifyOnDemand() {
func (cd *containerData) GetInfo(shouldUpdateSubcontainers bool) (*containerInfo, error) {
// Get spec and subcontainers.
if cd.clock.Since(cd.infoLastUpdatedTime) > 5*time.Second || shouldUpdateSubcontainers {
if cd.clock.Since(cd.infoLastUpdatedTime.Time()) > 5*time.Second || shouldUpdateSubcontainers {
err := cd.updateSpec()
if err != nil {
return nil, err
@ -183,7 +201,7 @@ func (cd *containerData) GetInfo(shouldUpdateSubcontainers bool) (*containerInfo
return nil, err
}
}
cd.infoLastUpdatedTime = cd.clock.Now()
cd.infoLastUpdatedTime.Store(cd.clock.Now().UnixNano())
}
cd.lock.Lock()
defer cd.lock.Unlock()
@ -594,9 +612,7 @@ func (cd *containerData) housekeepingTick(timer <-chan time.Time, longHousekeepi
klog.V(3).Infof("[%s] Housekeeping took %s", cd.info.Name, duration)
}
cd.notifyOnDemand()
cd.lock.Lock()
defer cd.lock.Unlock()
cd.statsLastUpdatedTime = cd.clock.Now()
cd.statsLastUpdatedTime.Store(cd.clock.Now().UnixNano())
return true
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// Manager of cAdvisor-monitored containers.
package manager
@ -191,7 +193,6 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, HousekeepingConfi
eventsChannel := make(chan watcher.ContainerEvent, 16)
newManager := &manager{
containers: make(map[namespacedContainerName]*containerData),
quitChannels: make([]chan error, 0, 2),
memoryCache: memoryCache,
fsInfo: fsInfo,
@ -245,9 +246,40 @@ type namespacedContainerName struct {
Name string
}
// containerMap is a type-safe wrapper around sync.Map for storing containerData
// keyed by namespacedContainerName.
type containerMap struct {
m sync.Map
}
// Load returns the containerData for the given name, or nil if not found.
func (c *containerMap) Load(name namespacedContainerName) (*containerData, bool) {
v, ok := c.m.Load(name)
if !ok {
return nil, false
}
return v.(*containerData), true
}
// Store stores the containerData for the given name.
func (c *containerMap) Store(name namespacedContainerName, data *containerData) {
c.m.Store(name, data)
}
// Delete removes the containerData for the given name.
func (c *containerMap) Delete(name namespacedContainerName) {
c.m.Delete(name)
}
// Range calls f for each container in the map. If f returns false, iteration stops.
func (c *containerMap) Range(f func(name namespacedContainerName, data *containerData) bool) {
c.m.Range(func(key, value any) bool {
return f(key.(namespacedContainerName), value.(*containerData))
})
}
type manager struct {
containers map[namespacedContainerName]*containerData
containersLock sync.RWMutex
containers containerMap
memoryCache *memory.InMemoryCache
fsInfo fs.FsInfo
sysFs sysfs.SysFs
@ -363,10 +395,14 @@ func (m *manager) Stop() error {
}
func (m *manager) destroyCollectors() {
for _, container := range m.containers {
m.containers.Range(func(_ namespacedContainerName, container *containerData) bool {
if container == nil {
return true
}
container.perfCollector.Destroy()
container.resctrlCollector.Destroy()
}
return true
})
}
func (m *manager) updateMachineInfo(quit chan error) {
@ -425,17 +461,8 @@ func (m *manager) globalHousekeeping(quit chan error) {
}
func (m *manager) getContainerData(containerName string) (*containerData, error) {
var cont *containerData
var ok bool
func() {
m.containersLock.RLock()
defer m.containersLock.RUnlock()
// Ensure we have the container.
cont, ok = m.containers[namespacedContainerName{
Name: containerName,
}]
}()
// Ensure we have the container.
cont, ok := m.containers.Load(namespacedContainerName{Name: containerName})
if !ok {
return nil, fmt.Errorf("unknown container %q", containerName)
}
@ -563,9 +590,7 @@ func (m *manager) containerDataToContainerInfo(cont *containerData, query *info.
}
func (m *manager) getContainer(containerName string) (*containerData, error) {
m.containersLock.RLock()
defer m.containersLock.RUnlock()
cont, ok := m.containers[namespacedContainerName{Name: containerName}]
cont, ok := m.containers.Load(namespacedContainerName{Name: containerName})
if !ok {
return nil, fmt.Errorf("unknown container %q", containerName)
}
@ -573,21 +598,20 @@ func (m *manager) getContainer(containerName string) (*containerData, error) {
}
func (m *manager) getSubcontainers(containerName string) map[string]*containerData {
m.containersLock.RLock()
defer m.containersLock.RUnlock()
containersMap := make(map[string]*containerData, len(m.containers))
matchedName := path.Join(containerName, "/")
containersMap := make(map[string]*containerData)
// Get all the unique subcontainers of the specified container
matchedName := path.Join(containerName, "/")
for i := range m.containers {
if m.containers[i] == nil {
continue
m.containers.Range(func(_ namespacedContainerName, cont *containerData) bool {
if cont == nil {
return true
}
name := m.containers[i].info.Name
name := cont.info.Name
if name == containerName || strings.HasPrefix(name, matchedName) {
containersMap[m.containers[i].info.Name] = m.containers[i]
containersMap[name] = cont
}
}
return true
})
return containersMap
}
@ -602,16 +626,18 @@ func (m *manager) SubcontainersInfo(containerName string, query *info.ContainerI
}
func (m *manager) getAllNamespacedContainers(ns string) map[string]*containerData {
m.containersLock.RLock()
defer m.containersLock.RUnlock()
containers := make(map[string]*containerData, len(m.containers))
containers := make(map[string]*containerData)
// Get containers in a namespace.
for name, cont := range m.containers {
m.containers.Range(func(name namespacedContainerName, cont *containerData) bool {
if cont == nil {
return true
}
if name.Namespace == ns {
containers[cont.info.Name] = cont
}
}
return true
})
return containers
}
@ -621,30 +647,32 @@ func (m *manager) AllDockerContainers(query *info.ContainerInfoRequest) (map[str
}
func (m *manager) namespacedContainer(containerName string, ns string) (*containerData, error) {
m.containersLock.RLock()
defer m.containersLock.RUnlock()
// Check for the container in the namespace.
cont, ok := m.containers[namespacedContainerName{
Namespace: ns,
Name: containerName,
}]
if cont, ok := m.containers.Load(namespacedContainerName{Namespace: ns, Name: containerName}); ok {
return cont, nil
}
// Look for container by short prefix name if no exact match found.
if !ok {
for contName, c := range m.containers {
if contName.Namespace == ns && strings.HasPrefix(contName.Name, containerName) {
if cont == nil {
cont = c
} else {
return nil, fmt.Errorf("unable to find container in %q namespace. Container %q is not unique", ns, containerName)
}
var cont *containerData
var err error
m.containers.Range(func(name namespacedContainerName, c *containerData) bool {
if name.Namespace == ns && strings.HasPrefix(name.Name, containerName) {
if cont == nil {
cont = c
} else {
err = fmt.Errorf("unable to find container in %q namespace. Container %q is not unique", ns, containerName)
return false // stop iteration
}
}
return true
})
if cont == nil {
return nil, fmt.Errorf("unable to find container %q in %q namespace", containerName, ns)
}
if err != nil {
return nil, err
}
if cont == nil {
return nil, fmt.Errorf("unable to find container %q in %q namespace", containerName, ns)
}
return cont, nil
@ -837,14 +865,7 @@ func (m *manager) GetVersionInfo() (*info.VersionInfo, error) {
}
func (m *manager) Exists(containerName string) bool {
m.containersLock.RLock()
defer m.containersLock.RUnlock()
namespacedName := namespacedContainerName{
Name: containerName,
}
_, ok := m.containers[namespacedName]
_, ok := m.containers.Load(namespacedContainerName{Name: containerName})
return ok
}
@ -858,7 +879,7 @@ func (m *manager) GetProcessList(containerName string, options v2.RequestOptions
return nil, err
}
if len(conts) != 1 {
return nil, fmt.Errorf("Expected the request to match only one container")
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{}
@ -904,19 +925,12 @@ func (m *manager) registerCollectors(collectorConfigs map[string]string, cont *c
// 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 {
if _, ok := m.containers.Load(namespacedName); ok {
return nil
}
@ -973,12 +987,12 @@ func (m *manager) createContainerLocked(containerName string, watchSource watche
}
// Add the container name and all its aliases. The aliases must be within the namespace of the factory.
m.containers[namespacedName] = cont
m.containers.Store(namespacedName, cont)
for _, alias := range cont.info.Aliases {
m.containers[namespacedContainerName{
m.containers.Store(namespacedContainerName{
Namespace: cont.info.Namespace,
Name: alias,
}] = cont
}, cont)
}
klog.V(3).Infof("Added container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace)
@ -1007,37 +1021,35 @@ func (m *manager) createContainerLocked(containerName string, watchSource watche
}
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]
cont, ok := m.containers.Load(namespacedName)
if !ok {
// Already destroyed, done.
return nil
}
// Tell the container to stop.
err := cont.Stop()
exitCode, err := cont.handler.GetExitCode()
if err != nil {
klog.V(4).Infof("Could not retrieve exit code for container %q: %v (using -1)", containerName, err)
exitCode = -1
}
err = cont.Stop()
if err != nil {
return err
}
// Remove the container from our records (and all its aliases).
delete(m.containers, namespacedName)
m.containers.Delete(namespacedName)
for _, alias := range cont.info.Aliases {
delete(m.containers, namespacedContainerName{
m.containers.Delete(namespacedContainerName{
Namespace: cont.info.Namespace,
Name: alias,
})
}
klog.V(3).Infof("Destroyed container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace)
klog.V(3).Infof("Destroyed container: %q (aliases: %v, namespace: %q, exit_code: %d)", containerName, cont.info.Aliases, cont.info.Namespace, exitCode)
contRef, err := cont.handler.ContainerReference()
if err != nil {
@ -1048,6 +1060,11 @@ func (m *manager) destroyContainerLocked(containerName string) error {
ContainerName: contRef.Name,
Timestamp: time.Now(),
EventType: info.EventContainerDeletion,
EventData: info.EventData{
ContainerDeletion: &info.ContainerDeletionEventData{
ExitCode: exitCode,
},
},
}
err = m.eventHandler.AddEvent(newEvent)
if err != nil {
@ -1059,11 +1076,7 @@ func (m *manager) destroyContainerLocked(containerName string) error {
// 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) {
// Get all subcontainers recursively.
m.containersLock.RLock()
cont, ok := m.containers[namespacedContainerName{
Name: containerName,
}]
m.containersLock.RUnlock()
cont, ok := m.containers.Load(namespacedContainerName{Name: containerName})
if !ok {
return nil, nil, fmt.Errorf("failed to find container %q while checking for new containers", containerName)
}
@ -1074,24 +1087,23 @@ func (m *manager) getContainersDiff(containerName string) (added []info.Containe
}
allContainers = append(allContainers, info.ContainerReference{Name: containerName})
m.containersLock.RLock()
defer m.containersLock.RUnlock()
// 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
m.containers.Range(func(name namespacedContainerName, cont *containerData) bool {
if cont == nil {
return true
}
}
// Only add the canonical name.
if cont.info.Name == name.Name {
allContainersSet[name.Name] = cont
}
return true
})
// Added containers
for _, c := range allContainers {
delete(allContainersSet, c.Name)
_, ok := m.containers[namespacedContainerName{
Name: c.Name,
}]
_, ok := m.containers.Load(namespacedContainerName{Name: c.Name})
if !ok {
added = append(added, c)
}
@ -1322,16 +1334,13 @@ 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{}{}
conts := make(map[*containerData]struct{})
m.containers.Range(func(_ namespacedContainerName, cont *containerData) bool {
if cont != nil {
conts[cont] = struct{}{}
}
}()
return true
})
// List containers.
lines := make([]string, 0, len(conts))

View file

@ -135,6 +135,12 @@ func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc, includedMetri
}}
},
},
{
name: "container_health_state",
help: "The result of the container's health check",
valueType: prometheus.GaugeValue,
getValues: getContainerHealthState,
},
},
includedMetrics: includedMetrics,
opts: opts,
@ -228,6 +234,30 @@ func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc, includedMetri
timestamp: s.Timestamp,
}}
},
}, {
name: "container_cpu_cfs_burst_periods_total",
help: "Number of periods when burst occurs.",
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.BurstsPeriods),
timestamp: s.Timestamp,
}}
},
}, {
name: "container_cpu_cfs_burst_seconds_total",
help: "Total time duration the container has been bursted.",
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.BurstTime) / float64(time.Second),
timestamp: s.Timestamp,
}}
},
},
}...)
}
@ -749,6 +779,54 @@ func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc, includedMetri
return float64(fs.WeightedIoTime) / float64(time.Second)
}, s.Timestamp)
},
}, {
name: "container_fs_io_cost_usage_seconds_total",
help: "Cumulative IOCost usage in seconds",
valueType: prometheus.CounterValue,
extraLabels: []string{"device"},
getValues: func(s *info.ContainerStats) metricValues {
return ioValues(
s.DiskIo.IoCostUsage, "Count", asMicrosecondsToSeconds,
[]info.FsStats{}, nil,
s.Timestamp,
)
},
}, {
name: "container_fs_io_cost_wait_seconds_total",
help: "Cumulative IOCost wait in seconds",
valueType: prometheus.CounterValue,
extraLabels: []string{"device"},
getValues: func(s *info.ContainerStats) metricValues {
return ioValues(
s.DiskIo.IoCostWait, "Count", asMicrosecondsToSeconds,
[]info.FsStats{}, nil,
s.Timestamp,
)
},
}, {
name: "container_fs_io_cost_indebt_seconds_total",
help: "Cumulative IOCost debt in seconds",
valueType: prometheus.CounterValue,
extraLabels: []string{"device"},
getValues: func(s *info.ContainerStats) metricValues {
return ioValues(
s.DiskIo.IoCostIndebt, "Count", asMicrosecondsToSeconds,
[]info.FsStats{}, nil,
s.Timestamp,
)
},
}, {
name: "container_fs_io_cost_indelay_seconds_total",
help: "Cumulative IOCost delay in seconds",
valueType: prometheus.CounterValue,
extraLabels: []string{"device"},
getValues: func(s *info.ContainerStats) metricValues {
return ioValues(
s.DiskIo.IoCostIndelay, "Count", asMicrosecondsToSeconds,
[]info.FsStats{}, nil,
s.Timestamp,
)
},
},
{
name: "container_blkio_device_usage_total",
@ -2091,3 +2169,18 @@ func getMinCoreScalingRatio(s *info.ContainerStats) metricValues {
}
return values
}
func getContainerHealthState(s *info.ContainerStats) metricValues {
value := float64(0)
switch s.Health.Status {
case "healthy":
value = 1
case "": // if container has no health check defined
value = -1
default: // starting or unhealthy
}
return metricValues{{
value: value,
timestamp: s.Timestamp,
}}
}

View file

@ -320,6 +320,8 @@ func (p testSubcontainersInfoProvider) GetRequestedContainersInfo(string, v2.Req
Periods: 723,
ThrottledPeriods: 18,
ThrottledTime: 1724314000,
BurstsPeriods: 25,
BurstTime: 500000000,
},
Schedstat: info.CpuSchedstat{
RunTime: 53643567,
@ -578,6 +580,30 @@ func (p testSubcontainersInfoProvider) GetRequestedContainersInfo(string, v2.Req
"Write": 6,
},
}},
IoCostUsage: []info.PerDiskStats{{
Device: "sda1",
Major: 8,
Minor: 1,
Stats: map[string]uint64{"Count": 1500000},
}},
IoCostWait: []info.PerDiskStats{{
Device: "sda1",
Major: 8,
Minor: 1,
Stats: map[string]uint64{"Count": 2500000},
}},
IoCostIndebt: []info.PerDiskStats{{
Device: "sda1",
Major: 8,
Minor: 1,
Stats: map[string]uint64{"Count": 500000},
}},
IoCostIndelay: []info.PerDiskStats{{
Device: "sda1",
Major: 8,
Minor: 1,
Stats: map[string]uint64{"Count": 750000},
}},
PSI: info.PSIStats{
Full: info.PSIData{
Avg10: 0.3,
@ -778,6 +804,7 @@ func (p testSubcontainersInfoProvider) GetRequestedContainersInfo(string, v2.Req
},
},
CpuSet: info.CPUSetStats{MemoryMigrate: 1},
Health: info.Health{Status: "healthy"},
},
},
},
@ -791,14 +818,14 @@ type erroringSubcontainersInfoProvider struct {
func (p *erroringSubcontainersInfoProvider) GetVersionInfo() (*info.VersionInfo, error) {
if p.shouldFail {
return nil, errors.New("Oops 1")
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 nil, errors.New("oops 2")
}
return p.successfulProvider.GetMachineInfo()
}
@ -806,7 +833,7 @@ func (p *erroringSubcontainersInfoProvider) GetMachineInfo() (*info.MachineInfo,
func (p *erroringSubcontainersInfoProvider) GetRequestedContainersInfo(
a string, opt v2.RequestOptions) (map[string]*info.ContainerInfo, error) {
if p.shouldFail {
return map[string]*info.ContainerInfo{}, errors.New("Oops 3")
return map[string]*info.ContainerInfo{}, errors.New("oops 3")
}
return p.successfulProvider.GetRequestedContainersInfo(a, opt)
}

View file

@ -1,5 +1,4 @@
//go:build libipmctl && cgo
// +build libipmctl,cgo
// Copyright 2020 Google Inc. All Rights Reserved.
//

View file

@ -1,5 +1,4 @@
//go:build !libipmctl || !cgo
// +build !libipmctl !cgo
// Copyright 2020 Google Inc. All Rights Reserved.
//

View file

@ -1,5 +1,4 @@
//go:build libpfm && cgo
// +build libpfm,cgo
// Copyright 2020 Google Inc. All Rights Reserved.
//

View file

@ -1,5 +1,4 @@
//go:build !libpfm || !cgo
// +build !libpfm !cgo
// Copyright 2020 Google Inc. All Rights Reserved.
//

View file

@ -1,5 +1,4 @@
//go:build libpfm && cgo
// +build libpfm,cgo
// Copyright 2020 Google Inc. All Rights Reserved.
//

View file

@ -1,5 +1,4 @@
//go:build !libpfm || !cgo
// +build !libpfm !cgo
// Copyright 2020 Google Inc. All Rights Reserved.
//

View file

@ -1,5 +1,4 @@
//go:build libpfm && cgo
// +build libpfm,cgo
// Copyright 2020 Google Inc. All Rights Reserved.
//

View file

@ -1,5 +1,4 @@
//go:build libpfm && cgo
// +build libpfm,cgo
// Copyright 2020 Google Inc. All Rights Reserved.
//

View file

@ -109,6 +109,7 @@ func (r *resource) AddSample(val uint64) {
Fifty: val,
Ninety: val,
NinetyFive: val,
Count: 1,
}
r.Add(sample)
}
@ -121,6 +122,8 @@ func (r *resource) GetAllPercentiles() info.Percentiles {
p.Fifty = r.samples.GetPercentile(0.5)
p.Ninety = r.samples.GetPercentile(0.9)
p.NinetyFive = r.samples.GetPercentile(0.95)
// len(samples) is equal to count stored in mean.
p.Count = r.mean.count
p.Present = true
return p
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package cpuload
import (

View file

@ -0,0 +1,40 @@
// 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:build !linux
package cpuload
import (
"fmt"
info "github.com/google/cadvisor/info/v1"
)
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) {
return nil, fmt.Errorf("cpuload is not supported on this platform")
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package netlink
import (

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package netlink
import (

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package netlink
import (

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package oomparser
import (

View file

@ -1,5 +1,4 @@
//go:build !x86
// +build !x86
// Copyright 2021 Google Inc. All Rights Reserved.
//

View file

@ -1,5 +1,4 @@
//go:build x86
// +build x86
// Copyright 2021 Google Inc. All Rights Reserved.
//

View file

@ -274,7 +274,7 @@ func getCPUTopology(sysFs sysfs.SysFs) ([]info.Node, int, error) {
cpusCount := len(cpusPaths)
if cpusCount == 0 {
err = fmt.Errorf("Any CPU is not available, cpusPath: %s", cpusPath)
err = fmt.Errorf("no CPU is available, cpusPath: %s", cpusPath)
return nil, 0, err
}

View file

@ -1,19 +0,0 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
examples/remove-empty-directories/remove-empty-directories
examples/sizes/sizes
examples/walk-fast/walk-fast
examples/walk-stdlib/walk-stdlib

View file

@ -1,25 +0,0 @@
BSD 2-Clause License
Copyright (c) 2017, Karrick McDermott
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.
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 HOLDER 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.

View file

@ -1,324 +0,0 @@
# godirwalk
`godirwalk` is a library for traversing a directory tree on a file
system.
[![GoDoc](https://godoc.org/github.com/karrick/godirwalk?status.svg)](https://godoc.org/github.com/karrick/godirwalk) [![Build Status](https://dev.azure.com/microsoft0235/microsoft/_apis/build/status/karrick.godirwalk?branchName=master)](https://dev.azure.com/microsoft0235/microsoft/_build/latest?definitionId=1&branchName=master)
In short, why did I create this library?
1. It's faster than `filepath.Walk`.
1. It's more correct on Windows than `filepath.Walk`.
1. It's more easy to use than `filepath.Walk`.
1. It's more flexible than `filepath.Walk`.
Depending on your specific circumstances, [you might no longer need a
library for file walking in
Go](https://engineering.kablamo.com.au/posts/2021/quick-comparison-between-go-file-walk-implementations).
## Usage Example
Additional examples are provided in the `examples/` subdirectory.
This library will normalize the provided top level directory name
based on the os-specific path separator by calling `filepath.Clean` on
its first argument. However it always provides the pathname created by
using the correct os-specific path separator when invoking the
provided callback function.
```Go
dirname := "some/directory/root"
err := godirwalk.Walk(dirname, &godirwalk.Options{
Callback: func(osPathname string, de *godirwalk.Dirent) error {
// Following string operation is not most performant way
// of doing this, but common enough to warrant a simple
// example here:
if strings.Contains(osPathname, ".git") {
return godirwalk.SkipThis
}
fmt.Printf("%s %s\n", de.ModeType(), osPathname)
return nil
},
Unsorted: true, // (optional) set true for faster yet non-deterministic enumeration (see godoc)
})
```
This library not only provides functions for traversing a file system
directory tree, but also for obtaining a list of immediate descendants
of a particular directory, typically much more quickly than using
`os.ReadDir` or `os.ReadDirnames`.
## Description
Here's why I use `godirwalk` in preference to `filepath.Walk`,
`os.ReadDir`, and `os.ReadDirnames`.
### It's faster than `filepath.Walk`
When compared against `filepath.Walk` in benchmarks, it has been
observed to run between five and ten times the speed on darwin, at
speeds comparable to the that of the unix `find` utility; and about
twice the speed on linux; and about four times the speed on Windows.
How does it obtain this performance boost? It does less work to give
you nearly the same output. This library calls the same `syscall`
functions to do the work, but it makes fewer calls, does not throw
away information that it might need, and creates less memory churn
along the way by reusing the same scratch buffer for reading from a
directory rather than reallocating a new buffer every time it reads
file system entry data from the operating system.
While traversing a file system directory tree, `filepath.Walk` obtains
the list of immediate descendants of a directory, and throws away the
node type information for the file system entry that is provided by
the operating system that comes with the node's name. Then,
immediately prior to invoking the callback function, `filepath.Walk`
invokes `os.Stat` for each node, and passes the returned `os.FileInfo`
information to the callback.
While the `os.FileInfo` information provided by `os.Stat` is extremely
helpful--and even includes the `os.FileMode` data--providing it
requires an additional system call for each node.
Because most callbacks only care about what the node type is, this
library does not throw the type information away, but rather provides
that information to the callback function in the form of a
`os.FileMode` value. Note that the provided `os.FileMode` value that
this library provides only has the node type information, and does not
have the permission bits, sticky bits, or other information from the
file's mode. If the callback does care about a particular node's
entire `os.FileInfo` data structure, the callback can easiy invoke
`os.Stat` when needed, and only when needed.
#### Benchmarks
##### macOS
```Bash
$ go test -bench=. -benchmem
goos: darwin
goarch: amd64
pkg: github.com/karrick/godirwalk
BenchmarkReadDirnamesStandardLibrary-12 50000 26250 ns/op 10360 B/op 16 allocs/op
BenchmarkReadDirnamesThisLibrary-12 50000 24372 ns/op 5064 B/op 20 allocs/op
BenchmarkFilepathWalk-12 1 1099524875 ns/op 228415912 B/op 416952 allocs/op
BenchmarkGodirwalk-12 2 526754589 ns/op 103110464 B/op 451442 allocs/op
BenchmarkGodirwalkUnsorted-12 3 509219296 ns/op 100751400 B/op 378800 allocs/op
BenchmarkFlameGraphFilepathWalk-12 1 7478618820 ns/op 2284138176 B/op 4169453 allocs/op
BenchmarkFlameGraphGodirwalk-12 1 4977264058 ns/op 1031105328 B/op 4514423 allocs/op
PASS
ok github.com/karrick/godirwalk 21.219s
```
##### Linux
```Bash
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: github.com/karrick/godirwalk
BenchmarkReadDirnamesStandardLibrary-12 100000 15458 ns/op 10360 B/op 16 allocs/op
BenchmarkReadDirnamesThisLibrary-12 100000 14646 ns/op 5064 B/op 20 allocs/op
BenchmarkFilepathWalk-12 2 631034745 ns/op 228210216 B/op 416939 allocs/op
BenchmarkGodirwalk-12 3 358714883 ns/op 102988664 B/op 451437 allocs/op
BenchmarkGodirwalkUnsorted-12 3 355363915 ns/op 100629234 B/op 378796 allocs/op
BenchmarkFlameGraphFilepathWalk-12 1 6086913991 ns/op 2282104720 B/op 4169417 allocs/op
BenchmarkFlameGraphGodirwalk-12 1 3456398824 ns/op 1029886400 B/op 4514373 allocs/op
PASS
ok github.com/karrick/godirwalk 19.179s
```
### It's more correct on Windows than `filepath.Walk`
I did not previously care about this either, but humor me. We all love
how we can write once and run everywhere. It is essential for the
language's adoption, growth, and success, that the software we create
can run unmodified on all architectures and operating systems
supported by Go.
When the traversed file system has a logical loop caused by symbolic
links to directories, on unix `filepath.Walk` ignores symbolic links
and traverses the entire directory tree without error. On Windows
however, `filepath.Walk` will continue following directory symbolic
links, even though it is not supposed to, eventually causing
`filepath.Walk` to terminate early and return an error when the
pathname gets too long from concatenating endless loops of symbolic
links onto the pathname. This error comes from Windows, passes through
`filepath.Walk`, and to the upstream client running `filepath.Walk`.
The takeaway is that behavior is different based on which platform
`filepath.Walk` is running. While this is clearly not intentional,
until it is fixed in the standard library, it presents a compatibility
problem.
This library fixes the above problem such that it will never follow
logical file sytem loops on either unix or Windows. Furthermore, it
will only follow symbolic links when `FollowSymbolicLinks` is set to
true. Behavior on Windows and other operating systems is identical.
### It's more easy to use than `filepath.Walk`
While this library strives to mimic the behavior of the incredibly
well-written `filepath.Walk` standard library, there are places where
it deviates a bit in order to provide a more easy or intuitive caller
interface.
#### Callback interface does not send you an error to check
Since this library does not invoke `os.Stat` on every file system node
it encounters, there is no possible error event for the callback
function to filter on. The third argument in the `filepath.WalkFunc`
function signature to pass the error from `os.Stat` to the callback
function is no longer necessary, and thus eliminated from signature of
the callback function from this library.
Furthermore, this slight interface difference between
`filepath.WalkFunc` and this library's `WalkFunc` eliminates the
boilerplate code that callback handlers must write when they use
`filepath.Walk`. Rather than every callback function needing to check
the error value passed into it and branch accordingly, users of this
library do not even have an error value to check immediately upon
entry into the callback function. This is an improvement both in
runtime performance and code clarity.
#### Callback function is invoked with OS specific file system path separator
On every OS platform `filepath.Walk` invokes the callback function
with a solidus (`/`) delimited pathname. By contrast this library
invokes the callback with the os-specific pathname separator,
obviating a call to `filepath.Clean` in the callback function for each
node prior to actually using the provided pathname.
In other words, even on Windows, `filepath.Walk` will invoke the
callback with `some/path/to/foo.txt`, requiring well written clients
to perform pathname normalization for every file prior to working with
the specified file. This is a hidden boilerplate requirement to create
truly os agnostic callback functions. In truth, many clients developed
on unix and not tested on Windows neglect this subtlety, and will
result in software bugs when someone tries to run that software on
Windows.
This library invokes the callback function with `some\path\to\foo.txt`
for the same file when running on Windows, eliminating the need to
normalize the pathname by the client, and lessen the likelyhood that a
client will work on unix but not on Windows.
This enhancement eliminates necessity for some more boilerplate code
in callback functions while improving the runtime performance of this
library.
#### `godirwalk.SkipThis` is more intuitive to use than `filepath.SkipDir`
One arguably confusing aspect of the `filepath.WalkFunc` interface
that this library must emulate is how a caller tells the `Walk`
function to skip file system entries. With both `filepath.Walk` and
this library's `Walk`, when a callback function wants to skip a
directory and not descend into its children, it returns
`filepath.SkipDir`. If the callback function returns
`filepath.SkipDir` for a non-directory, `filepath.Walk` and this
library will stop processing any more entries in the current
directory. This is not necessarily what most developers want or
expect. If you want to simply skip a particular non-directory entry
but continue processing entries in the directory, the callback
function must return nil.
The implications of this interface design is when you want to walk a
file system hierarchy and skip an entry, you have to return a
different value based on what type of file system entry that node
is. To skip an entry, if the entry is a directory, you must return
`filepath.SkipDir`, and if entry is not a directory, you must return
`nil`. This is an unfortunate hurdle I have observed many developers
struggling with, simply because it is not an intuitive interface.
Here is an example callback function that adheres to
`filepath.WalkFunc` interface to have it skip any file system entry
whose full pathname includes a particular substring, `optSkip`. Note
that this library still supports identical behavior of `filepath.Walk`
when the callback function returns `filepath.SkipDir`.
```Go
func callback1(osPathname string, de *godirwalk.Dirent) error {
if optSkip != "" && strings.Contains(osPathname, optSkip) {
if b, err := de.IsDirOrSymlinkToDir(); b == true && err == nil {
return filepath.SkipDir
}
return nil
}
// Process file like normal...
return nil
}
```
This library attempts to eliminate some of that logic boilerplate
required in callback functions by providing a new token error value,
`SkipThis`, which a callback function may return to skip the current
file system entry regardless of what type of entry it is. If the
current entry is a directory, its children will not be enumerated,
exactly as if the callback had returned `filepath.SkipDir`. If the
current entry is a non-directory, the next file system entry in the
current directory will be enumerated, exactly as if the callback
returned `nil`. The following example callback function has identical
behavior as the previous, but has less boilerplate, and admittedly
logic that I find more simple to follow.
```Go
func callback2(osPathname string, de *godirwalk.Dirent) error {
if optSkip != "" && strings.Contains(osPathname, optSkip) {
return godirwalk.SkipThis
}
// Process file like normal...
return nil
}
```
### It's more flexible than `filepath.Walk`
#### Configurable Handling of Symbolic Links
The default behavior of this library is to ignore symbolic links to
directories when walking a directory tree, just like `filepath.Walk`
does. However, it does invoke the callback function with each node it
finds, including symbolic links. If a particular use case exists to
follow symbolic links when traversing a directory tree, this library
can be invoked in manner to do so, by setting the
`FollowSymbolicLinks` config parameter to `true`.
#### Configurable Sorting of Directory Children
The default behavior of this library is to always sort the immediate
descendants of a directory prior to visiting each node, just like
`filepath.Walk` does. This is usually the desired behavior. However,
this does come at slight performance and memory penalties required to
sort the names when a directory node has many entries. Additionally if
caller specifies `Unsorted` enumeration in the configuration
parameter, reading directories is lazily performed as the caller
consumes entries. If a particular use case exists that does not
require sorting the directory's immediate descendants prior to
visiting its nodes, this library will skip the sorting step when the
`Unsorted` parameter is set to `true`.
Here's an interesting read of the potential hazzards of traversing a
file system hierarchy in a non-deterministic order. If you know the
problem you are solving is not affected by the order files are
visited, then I encourage you to use `Unsorted`. Otherwise skip
setting this option.
[Researchers find bug in Python script may have affected hundreds of studies](https://arstechnica.com/information-technology/2019/10/chemists-discover-cross-platform-python-scripts-not-so-cross-platform/)
#### Configurable Post Children Callback
This library provides upstream code with the ability to specify a
callback function to be invoked for each directory after its children
are processed. This has been used to recursively delete empty
directories after traversing the file system in a more efficient
manner. See the `examples/clean-empties` directory for an example of
this usage.
#### Configurable Error Callback
This library provides upstream code with the ability to specify a
callback to be invoked for errors that the operating system returns,
allowing the upstream code to determine the next course of action to
take, whether to halt walking the hierarchy, as it would do were no
error callback provided, or skip the node that caused the error. See
the `examples/walk-fast` directory for an example of this usage.

View file

@ -1,53 +0,0 @@
# Go
# Build your Go project.
# Add steps that test, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/go
trigger:
- master
variables:
GOVERSION: 1.13
jobs:
- job: Linux
pool:
vmImage: 'ubuntu-latest'
steps:
- task: GoTool@0
displayName: 'Use Go $(GOVERSION)'
inputs:
version: $(GOVERSION)
- task: Go@0
inputs:
command: test
arguments: -race -v ./...
displayName: 'Execute Tests'
- job: Mac
pool:
vmImage: 'macos-latest'
steps:
- task: GoTool@0
displayName: 'Use Go $(GOVERSION)'
inputs:
version: $(GOVERSION)
- task: Go@0
inputs:
command: test
arguments: -race -v ./...
displayName: 'Execute Tests'
- job: Windows
pool:
vmImage: 'windows-latest'
steps:
- task: GoTool@0
displayName: 'Use Go $(GOVERSION)'
inputs:
version: $(GOVERSION)
- task: Go@0
inputs:
command: test
arguments: -race -v ./...
displayName: 'Execute Tests'

View file

@ -1,7 +0,0 @@
#!/bin/bash
# for version in v1.9.1 v1.10.0 v1.10.3 v1.10.12 v1.11.2 v1.11.3 v1.12.0 v1.13.1 v1.14.0 v1.14.1 ; do
for version in v1.10.12 v1.14.1 v1.15.2 ; do
echo "### $version" > $version.txt
git checkout -- go.mod && git checkout $version && go test -run=NONE -bench=Benchmark2 >> $version.txt || exit 1
done

View file

@ -1,14 +0,0 @@
// +build godirwalk_debug
package godirwalk
import (
"fmt"
"os"
)
// debug formats and prints arguments to stderr for development builds
func debug(f string, a ...interface{}) {
// fmt.Fprintf(os.Stderr, f, a...)
os.Stderr.Write([]byte("godirwalk: " + fmt.Sprintf(f, a...)))
}

View file

@ -1,6 +0,0 @@
// +build !godirwalk_debug
package godirwalk
// debug is a no-op for release builds
func debug(_ string, _ ...interface{}) {}

View file

@ -1,104 +0,0 @@
package godirwalk
import (
"os"
"path/filepath"
)
// Dirent stores the name and file system mode type of discovered file system
// entries.
type Dirent struct {
name string // base name of the file system entry.
path string // path name of the file system entry.
modeType os.FileMode // modeType is the type of file system entry.
}
// NewDirent returns a newly initialized Dirent structure, or an error. This
// function does not follow symbolic links.
//
// This function is rarely used, as Dirent structures are provided by other
// functions in this library that read and walk directories, but is provided,
// however, for the occasion when a program needs to create a Dirent.
func NewDirent(osPathname string) (*Dirent, error) {
modeType, err := modeType(osPathname)
if err != nil {
return nil, err
}
return &Dirent{
name: filepath.Base(osPathname),
path: filepath.Dir(osPathname),
modeType: modeType,
}, nil
}
// IsDir returns true if and only if the Dirent represents a file system
// directory. Note that on some operating systems, more than one file mode bit
// may be set for a node. For instance, on Windows, a symbolic link that points
// to a directory will have both the directory and the symbolic link bits set.
func (de Dirent) IsDir() bool { return de.modeType&os.ModeDir != 0 }
// IsDirOrSymlinkToDir returns true if and only if the Dirent represents a file
// system directory, or a symbolic link to a directory. Note that if the Dirent
// is not a directory but is a symbolic link, this method will resolve by
// sending a request to the operating system to follow the symbolic link.
func (de Dirent) IsDirOrSymlinkToDir() (bool, error) {
if de.IsDir() {
return true, nil
}
if !de.IsSymlink() {
return false, nil
}
// Does this symlink point to a directory?
info, err := os.Stat(filepath.Join(de.path, de.name))
if err != nil {
return false, err
}
return info.IsDir(), nil
}
// IsRegular returns true if and only if the Dirent represents a regular file.
// That is, it ensures that no mode type bits are set.
func (de Dirent) IsRegular() bool { return de.modeType&os.ModeType == 0 }
// IsSymlink returns true if and only if the Dirent represents a file system
// symbolic link. Note that on some operating systems, more than one file mode
// bit may be set for a node. For instance, on Windows, a symbolic link that
// points to a directory will have both the directory and the symbolic link bits
// set.
func (de Dirent) IsSymlink() bool { return de.modeType&os.ModeSymlink != 0 }
// IsDevice returns true if and only if the Dirent represents a device file.
func (de Dirent) IsDevice() bool { return de.modeType&os.ModeDevice != 0 }
// ModeType returns the mode bits that specify the file system node type. We
// could make our own enum-like data type for encoding the file type, but Go's
// runtime already gives us architecture independent file modes, as discussed in
// `os/types.go`:
//
// Go's runtime FileMode type has same definition on all systems, so that
// information about files can be moved from one system to another portably.
func (de Dirent) ModeType() os.FileMode { return de.modeType }
// Name returns the base name of the file system entry.
func (de Dirent) Name() string { return de.name }
// reset releases memory held by entry err and name, and resets mode type to 0.
func (de *Dirent) reset() {
de.name = ""
de.path = ""
de.modeType = 0
}
// Dirents represents a slice of Dirent pointers, which are sortable by base
// name. This type satisfies the `sort.Interface` interface.
type Dirents []*Dirent
// Len returns the count of Dirent structures in the slice.
func (l Dirents) Len() int { return len(l) }
// Less returns true if and only if the base name of the element specified by
// the first index is lexicographically less than that of the second index.
func (l Dirents) Less(i, j int) bool { return l[i].name < l[j].name }
// Swap exchanges the two Dirent entries specified by the two provided indexes.
func (l Dirents) Swap(i, j int) { l[i], l[j] = l[j], l[i] }

View file

@ -1,42 +0,0 @@
/*
Package godirwalk provides functions to read and traverse directory trees.
In short, why do I use this library?
* It's faster than `filepath.Walk`.
* It's more correct on Windows than `filepath.Walk`.
* It's more easy to use than `filepath.Walk`.
* It's more flexible than `filepath.Walk`.
USAGE
This library will normalize the provided top level directory name based on the
os-specific path separator by calling `filepath.Clean` on its first
argument. However it always provides the pathname created by using the correct
os-specific path separator when invoking the provided callback function.
dirname := "some/directory/root"
err := godirwalk.Walk(dirname, &godirwalk.Options{
Callback: func(osPathname string, de *godirwalk.Dirent) error {
fmt.Printf("%s %s\n", de.ModeType(), osPathname)
return nil
},
})
This library not only provides functions for traversing a file system directory
tree, but also for obtaining a list of immediate descendants of a particular
directory, typically much more quickly than using `os.ReadDir` or
`os.ReadDirnames`.
scratchBuffer := make([]byte, godirwalk.MinimumScratchBufferSize)
names, err := godirwalk.ReadDirnames("some/directory", scratchBuffer)
// ...
entries, err := godirwalk.ReadDirents("another/directory", scratchBuffer)
// ...
*/
package godirwalk

View file

@ -1,9 +0,0 @@
// +build dragonfly freebsd openbsd netbsd
package godirwalk
import "syscall"
func inoFromDirent(de *syscall.Dirent) uint64 {
return uint64(de.Fileno)
}

View file

@ -1,9 +0,0 @@
// +build aix darwin linux nacl solaris
package godirwalk
import "syscall"
func inoFromDirent(de *syscall.Dirent) uint64 {
return uint64(de.Ino)
}

View file

@ -1,22 +0,0 @@
package godirwalk
import (
"os"
)
// modeType returns the mode type of the file system entry identified by
// osPathname by calling os.LStat function, to intentionally not follow symbolic
// links.
//
// Even though os.LStat provides all file mode bits, we want to ensure same
// values returned to caller regardless of whether we obtained file mode bits
// from syscall or stat call. Therefore mask out the additional file mode bits
// that are provided by stat but not by the syscall, so users can rely on their
// values.
func modeType(osPathname string) (os.FileMode, error) {
fi, err := os.Lstat(osPathname)
if err == nil {
return fi.Mode() & os.ModeType, nil
}
return 0, err
}

View file

@ -1,37 +0,0 @@
// +build darwin dragonfly freebsd linux netbsd openbsd
package godirwalk
import (
"os"
"path/filepath"
"syscall"
)
// modeTypeFromDirent converts a syscall defined constant, which is in purview
// of OS, to a constant defined by Go, assumed by this project to be stable.
//
// When the syscall constant is not recognized, this function falls back to a
// Stat on the file system.
func modeTypeFromDirent(de *syscall.Dirent, osDirname, osBasename string) (os.FileMode, error) {
switch de.Type {
case syscall.DT_REG:
return 0, nil
case syscall.DT_DIR:
return os.ModeDir, nil
case syscall.DT_LNK:
return os.ModeSymlink, nil
case syscall.DT_CHR:
return os.ModeDevice | os.ModeCharDevice, nil
case syscall.DT_BLK:
return os.ModeDevice, nil
case syscall.DT_FIFO:
return os.ModeNamedPipe, nil
case syscall.DT_SOCK:
return os.ModeSocket, nil
default:
// If syscall returned unknown type (e.g., DT_UNKNOWN, DT_WHT), then
// resolve actual mode by reading file information.
return modeType(filepath.Join(osDirname, osBasename))
}
}

View file

@ -1,18 +0,0 @@
// +build aix js nacl solaris
package godirwalk
import (
"os"
"path/filepath"
"syscall"
)
// modeTypeFromDirent converts a syscall defined constant, which is in purview
// of OS, to a constant defined by Go, assumed by this project to be stable.
//
// Because some operating system syscall.Dirent structures do not include a Type
// field, fall back on Stat of the file system.
func modeTypeFromDirent(_ *syscall.Dirent, osDirname, osBasename string) (os.FileMode, error) {
return modeType(filepath.Join(osDirname, osBasename))
}

View file

@ -1,29 +0,0 @@
// +build aix darwin dragonfly freebsd netbsd openbsd
package godirwalk
import (
"reflect"
"syscall"
"unsafe"
)
func nameFromDirent(de *syscall.Dirent) []byte {
// Because this GOOS' syscall.Dirent provides a Namlen field that says how
// long the name is, this function does not need to search for the NULL
// byte.
ml := int(de.Namlen)
// Convert syscall.Dirent.Name, which is array of int8, to []byte, by
// overwriting Cap, Len, and Data slice header fields to values from
// syscall.Dirent fields. Setting the Cap, Len, and Data field values for
// the slice header modifies what the slice header points to, and in this
// case, the name buffer.
var name []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&name))
sh.Cap = ml
sh.Len = ml
sh.Data = uintptr(unsafe.Pointer(&de.Name[0]))
return name
}

View file

@ -1,42 +0,0 @@
// +build nacl linux js solaris
package godirwalk
import (
"bytes"
"reflect"
"syscall"
"unsafe"
)
// nameOffset is a compile time constant
const nameOffset = int(unsafe.Offsetof(syscall.Dirent{}.Name))
func nameFromDirent(de *syscall.Dirent) (name []byte) {
// Because this GOOS' syscall.Dirent does not provide a field that specifies
// the name length, this function must first calculate the max possible name
// length, and then search for the NULL byte.
ml := int(de.Reclen) - nameOffset
// Convert syscall.Dirent.Name, which is array of int8, to []byte, by
// overwriting Cap, Len, and Data slice header fields to the max possible
// name length computed above, and finding the terminating NULL byte.
sh := (*reflect.SliceHeader)(unsafe.Pointer(&name))
sh.Cap = ml
sh.Len = ml
sh.Data = uintptr(unsafe.Pointer(&de.Name[0]))
if index := bytes.IndexByte(name, 0); index >= 0 {
// Found NULL byte; set slice's cap and len accordingly.
sh.Cap = index
sh.Len = index
return
}
// NOTE: This branch is not expected, but included for defensive
// programming, and provides a hard stop on the name based on the structure
// field array size.
sh.Cap = len(de.Name)
sh.Len = sh.Cap
return
}

View file

@ -1,53 +0,0 @@
package godirwalk
// ReadDirents returns a sortable slice of pointers to Dirent structures, each
// representing the file system name and mode type for one of the immediate
// descendant of the specified directory. If the specified directory is a
// symbolic link, it will be resolved.
//
// If an optional scratch buffer is provided that is at least one page of
// memory, it will be used when reading directory entries from the file
// system. If you plan on calling this function in a loop, you will have
// significantly better performance if you allocate a scratch buffer and use it
// each time you call this function.
//
// children, err := godirwalk.ReadDirents(osDirname, nil)
// if err != nil {
// return nil, errors.Wrap(err, "cannot get list of directory children")
// }
// sort.Sort(children)
// for _, child := range children {
// fmt.Printf("%s %s\n", child.ModeType, child.Name)
// }
func ReadDirents(osDirname string, scratchBuffer []byte) (Dirents, error) {
return readDirents(osDirname, scratchBuffer)
}
// ReadDirnames returns a slice of strings, representing the immediate
// descendants of the specified directory. If the specified directory is a
// symbolic link, it will be resolved.
//
// If an optional scratch buffer is provided that is at least one page of
// memory, it will be used when reading directory entries from the file
// system. If you plan on calling this function in a loop, you will have
// significantly better performance if you allocate a scratch buffer and use it
// each time you call this function.
//
// Note that this function, depending on operating system, may or may not invoke
// the ReadDirents function, in order to prepare the list of immediate
// descendants. Therefore, if your program needs both the names and the file
// system mode types of descendants, it will always be faster to invoke
// ReadDirents directly, rather than calling this function, then looping over
// the results and calling os.Stat or os.LStat for each entry.
//
// children, err := godirwalk.ReadDirnames(osDirname, nil)
// if err != nil {
// return nil, errors.Wrap(err, "cannot get list of directory children")
// }
// sort.Strings(children)
// for _, child := range children {
// fmt.Printf("%s\n", child)
// }
func ReadDirnames(osDirname string, scratchBuffer []byte) ([]string, error) {
return readDirnames(osDirname, scratchBuffer)
}

View file

@ -1,131 +0,0 @@
// +build !windows
package godirwalk
import (
"os"
"syscall"
"unsafe"
)
// MinimumScratchBufferSize specifies the minimum size of the scratch buffer
// that ReadDirents, ReadDirnames, Scanner, and Walk will use when reading file
// entries from the operating system. During program startup it is initialized
// to the result from calling `os.Getpagesize()` for non Windows environments,
// and 0 for Windows.
var MinimumScratchBufferSize = os.Getpagesize()
func newScratchBuffer() []byte { return make([]byte, MinimumScratchBufferSize) }
func readDirents(osDirname string, scratchBuffer []byte) ([]*Dirent, error) {
var entries []*Dirent
var workBuffer []byte
dh, err := os.Open(osDirname)
if err != nil {
return nil, err
}
fd := int(dh.Fd())
if len(scratchBuffer) < MinimumScratchBufferSize {
scratchBuffer = newScratchBuffer()
}
var sde syscall.Dirent
for {
if len(workBuffer) == 0 {
n, err := syscall.ReadDirent(fd, scratchBuffer)
// n, err := unix.ReadDirent(fd, scratchBuffer)
if err != nil {
if err == syscall.EINTR /* || err == unix.EINTR */ {
continue
}
_ = dh.Close()
return nil, err
}
if n <= 0 { // end of directory: normal exit
if err = dh.Close(); err != nil {
return nil, err
}
return entries, nil
}
workBuffer = scratchBuffer[:n] // trim work buffer to number of bytes read
}
copy((*[unsafe.Sizeof(syscall.Dirent{})]byte)(unsafe.Pointer(&sde))[:], workBuffer)
workBuffer = workBuffer[reclen(&sde):] // advance buffer for next iteration through loop
if inoFromDirent(&sde) == 0 {
continue // inode set to 0 indicates an entry that was marked as deleted
}
nameSlice := nameFromDirent(&sde)
nameLength := len(nameSlice)
if nameLength == 0 || (nameSlice[0] == '.' && (nameLength == 1 || (nameLength == 2 && nameSlice[1] == '.'))) {
continue
}
childName := string(nameSlice)
mt, err := modeTypeFromDirent(&sde, osDirname, childName)
if err != nil {
_ = dh.Close()
return nil, err
}
entries = append(entries, &Dirent{name: childName, path: osDirname, modeType: mt})
}
}
func readDirnames(osDirname string, scratchBuffer []byte) ([]string, error) {
var entries []string
var workBuffer []byte
var sde *syscall.Dirent
dh, err := os.Open(osDirname)
if err != nil {
return nil, err
}
fd := int(dh.Fd())
if len(scratchBuffer) < MinimumScratchBufferSize {
scratchBuffer = newScratchBuffer()
}
for {
if len(workBuffer) == 0 {
n, err := syscall.ReadDirent(fd, scratchBuffer)
// n, err := unix.ReadDirent(fd, scratchBuffer)
if err != nil {
if err == syscall.EINTR /* || err == unix.EINTR */ {
continue
}
_ = dh.Close()
return nil, err
}
if n <= 0 { // end of directory: normal exit
if err = dh.Close(); err != nil {
return nil, err
}
return entries, nil
}
workBuffer = scratchBuffer[:n] // trim work buffer to number of bytes read
}
sde = (*syscall.Dirent)(unsafe.Pointer(&workBuffer[0])) // point entry to first syscall.Dirent in buffer
// Handle first entry in the work buffer.
workBuffer = workBuffer[reclen(sde):] // advance buffer for next iteration through loop
if inoFromDirent(sde) == 0 {
continue // inode set to 0 indicates an entry that was marked as deleted
}
nameSlice := nameFromDirent(sde)
nameLength := len(nameSlice)
if nameLength == 0 || (nameSlice[0] == '.' && (nameLength == 1 || (nameLength == 2 && nameSlice[1] == '.'))) {
continue
}
entries = append(entries, string(nameSlice))
}
}

View file

@ -1,66 +0,0 @@
// +build windows
package godirwalk
import "os"
// MinimumScratchBufferSize specifies the minimum size of the scratch buffer
// that ReadDirents, ReadDirnames, Scanner, and Walk will use when reading file
// entries from the operating system. During program startup it is initialized
// to the result from calling `os.Getpagesize()` for non Windows environments,
// and 0 for Windows.
var MinimumScratchBufferSize = 0
func newScratchBuffer() []byte { return nil }
func readDirents(osDirname string, _ []byte) ([]*Dirent, error) {
dh, err := os.Open(osDirname)
if err != nil {
return nil, err
}
fileinfos, err := dh.Readdir(-1)
if err != nil {
_ = dh.Close()
return nil, err
}
entries := make([]*Dirent, len(fileinfos))
for i, fi := range fileinfos {
entries[i] = &Dirent{
name: fi.Name(),
path: osDirname,
modeType: fi.Mode() & os.ModeType,
}
}
if err = dh.Close(); err != nil {
return nil, err
}
return entries, nil
}
func readDirnames(osDirname string, _ []byte) ([]string, error) {
dh, err := os.Open(osDirname)
if err != nil {
return nil, err
}
fileinfos, err := dh.Readdir(-1)
if err != nil {
_ = dh.Close()
return nil, err
}
entries := make([]string, len(fileinfos))
for i, fi := range fileinfos {
entries[i] = fi.Name()
}
if err = dh.Close(); err != nil {
return nil, err
}
return entries, nil
}

View file

@ -1,9 +0,0 @@
// +build dragonfly
package godirwalk
import "syscall"
func reclen(de *syscall.Dirent) uint64 {
return (16 + uint64(de.Namlen) + 1 + 7) &^ 7
}

Some files were not shown because too many files have changed in this diff Show more