mirror of https://github.com/grafana/loki
limits: limits implementation for loki (#948)
* limits implementation for loki Added following new limits: Length of query Number of active streams Number of streams matcher per query Ingestion rate * periodically reload ingestion rate limits to apply latest rates from overridespull/1043/head
parent
b30c0d31bf
commit
ec5bc7054d
@ -0,0 +1,226 @@ |
||||
package validation |
||||
|
||||
import ( |
||||
"flag" |
||||
"os" |
||||
"time" |
||||
|
||||
"github.com/cortexproject/cortex/pkg/util/validation" |
||||
|
||||
"gopkg.in/yaml.v2" |
||||
) |
||||
|
||||
// Limits describe all the limits for users; can be used to describe global default
|
||||
// limits via flags, or per-user limits via yaml config.
|
||||
type Limits struct { |
||||
// Distributor enforced limits.
|
||||
IngestionRate float64 `yaml:"ingestion_rate_mb"` |
||||
IngestionBurstSize float64 `yaml:"ingestion_burst_size_mb"` |
||||
MaxLabelNameLength int `yaml:"max_label_name_length"` |
||||
MaxLabelValueLength int `yaml:"max_label_value_length"` |
||||
MaxLabelNamesPerSeries int `yaml:"max_label_names_per_series"` |
||||
RejectOldSamples bool `yaml:"reject_old_samples"` |
||||
RejectOldSamplesMaxAge time.Duration `yaml:"reject_old_samples_max_age"` |
||||
CreationGracePeriod time.Duration `yaml:"creation_grace_period"` |
||||
EnforceMetricName bool `yaml:"enforce_metric_name"` |
||||
|
||||
// Ingester enforced limits.
|
||||
MaxStreamsPerUser int `yaml:"max_streams_per_user"` |
||||
|
||||
// Querier enforced limits.
|
||||
MaxChunksPerQuery int `yaml:"max_chunks_per_query"` |
||||
MaxQueryLength time.Duration `yaml:"max_query_length"` |
||||
MaxQueryParallelism int `yaml:"max_query_parallelism"` |
||||
CardinalityLimit int `yaml:"cardinality_limit"` |
||||
MaxStreamsMatchersPerQuery int `yaml:"max_streams_matchers_per_query"` |
||||
|
||||
// Config for overrides, convenient if it goes here.
|
||||
PerTenantOverrideConfig string `yaml:"per_tenant_override_config"` |
||||
PerTenantOverridePeriod time.Duration `yaml:"per_tenant_override_period"` |
||||
} |
||||
|
||||
// RegisterFlags adds the flags required to config this to the given FlagSet
|
||||
func (l *Limits) RegisterFlags(f *flag.FlagSet) { |
||||
f.Float64Var(&l.IngestionRate, "distributor.ingestion-rate-limit-mb", 4, "Per-user ingestion rate limit in sample size per second. Units in MB.") |
||||
f.Float64Var(&l.IngestionBurstSize, "distributor.ingestion-burst-size-mb", 6, "Per-user allowed ingestion burst size (in sample size). Units in MB. Warning, very high limits will be reset every -distributor.limiter-reload-period.") |
||||
f.IntVar(&l.MaxLabelNameLength, "validation.max-length-label-name", 1024, "Maximum length accepted for label names") |
||||
f.IntVar(&l.MaxLabelValueLength, "validation.max-length-label-value", 2048, "Maximum length accepted for label value. This setting also applies to the metric name") |
||||
f.IntVar(&l.MaxLabelNamesPerSeries, "validation.max-label-names-per-series", 30, "Maximum number of label names per series.") |
||||
f.BoolVar(&l.RejectOldSamples, "validation.reject-old-samples", false, "Reject old samples.") |
||||
f.DurationVar(&l.RejectOldSamplesMaxAge, "validation.reject-old-samples.max-age", 14*24*time.Hour, "Maximum accepted sample age before rejecting.") |
||||
f.DurationVar(&l.CreationGracePeriod, "validation.create-grace-period", 10*time.Minute, "Duration which table will be created/deleted before/after it's needed; we won't accept sample from before this time.") |
||||
f.BoolVar(&l.EnforceMetricName, "validation.enforce-metric-name", true, "Enforce every sample has a metric name.") |
||||
|
||||
f.IntVar(&l.MaxStreamsPerUser, "ingester.max-streams-per-user", 10e3, "Maximum number of active streams per user.") |
||||
|
||||
f.IntVar(&l.MaxChunksPerQuery, "store.query-chunk-limit", 2e6, "Maximum number of chunks that can be fetched in a single query.") |
||||
f.DurationVar(&l.MaxQueryLength, "store.max-query-length", 0, "Limit to length of chunk store queries, 0 to disable.") |
||||
f.IntVar(&l.MaxQueryParallelism, "querier.max-query-parallelism", 14, "Maximum number of queries will be scheduled in parallel by the frontend.") |
||||
f.IntVar(&l.CardinalityLimit, "store.cardinality-limit", 1e5, "Cardinality limit for index queries.") |
||||
f.IntVar(&l.MaxStreamsMatchersPerQuery, "querier.max-streams-matcher-per-query", 1000, "Limit the number of streams matchers per query") |
||||
|
||||
f.StringVar(&l.PerTenantOverrideConfig, "limits.per-user-override-config", "", "File name of per-user overrides.") |
||||
f.DurationVar(&l.PerTenantOverridePeriod, "limits.per-user-override-period", 10*time.Second, "Period with this to reload the overrides.") |
||||
} |
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (l *Limits) UnmarshalYAML(unmarshal func(interface{}) error) error { |
||||
// We want to set c to the defaults and then overwrite it with the input.
|
||||
// To make unmarshal fill the plain data struct rather than calling UnmarshalYAML
|
||||
// again, we have to hide it using a type indirection. See prometheus/config.
|
||||
|
||||
// During startup we wont have a default value so we don't want to overwrite them
|
||||
if defaultLimits != nil { |
||||
*l = *defaultLimits |
||||
} |
||||
type plain Limits |
||||
return unmarshal((*plain)(l)) |
||||
} |
||||
|
||||
// When we load YAML from disk, we want the various per-customer limits
|
||||
// to default to any values specified on the command line, not default
|
||||
// command line values. This global contains those values. I (Tom) cannot
|
||||
// find a nicer way I'm afraid.
|
||||
var defaultLimits *Limits |
||||
|
||||
// Overrides periodically fetch a set of per-user overrides, and provides convenience
|
||||
// functions for fetching the correct value.
|
||||
type Overrides struct { |
||||
overridesManager *validation.OverridesManager |
||||
} |
||||
|
||||
// NewOverrides makes a new Overrides.
|
||||
// We store the supplied limits in a global variable to ensure per-tenant limits
|
||||
// are defaulted to those values. As such, the last call to NewOverrides will
|
||||
// become the new global defaults.
|
||||
func NewOverrides(defaults Limits) (*Overrides, error) { |
||||
defaultLimits = &defaults |
||||
overridesManagerConfig := validation.OverridesManagerConfig{ |
||||
OverridesReloadPeriod: defaults.PerTenantOverridePeriod, |
||||
OverridesLoadPath: defaults.PerTenantOverrideConfig, |
||||
OverridesLoader: loadOverrides, |
||||
Defaults: &defaults, |
||||
} |
||||
|
||||
overridesManager, err := validation.NewOverridesManager(overridesManagerConfig) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &Overrides{ |
||||
overridesManager: overridesManager, |
||||
}, nil |
||||
} |
||||
|
||||
// Stop background reloading of overrides.
|
||||
func (o *Overrides) Stop() { |
||||
o.overridesManager.Stop() |
||||
} |
||||
|
||||
// IngestionRate returns the limit on ingester rate (samples per second).
|
||||
func (o *Overrides) IngestionRate(userID string) float64 { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).IngestionRate |
||||
} |
||||
|
||||
// IngestionBurstSize returns the burst size for ingestion rate.
|
||||
func (o *Overrides) IngestionBurstSize(userID string) float64 { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).IngestionBurstSize |
||||
} |
||||
|
||||
// MaxLabelNameLength returns maximum length a label name can be.
|
||||
func (o *Overrides) MaxLabelNameLength(userID string) int { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).MaxLabelNameLength |
||||
} |
||||
|
||||
// MaxLabelValueLength returns maximum length a label value can be. This also is
|
||||
// the maximum length of a metric name.
|
||||
func (o *Overrides) MaxLabelValueLength(userID string) int { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).MaxLabelValueLength |
||||
} |
||||
|
||||
// MaxLabelNamesPerSeries returns maximum number of label/value pairs timeseries.
|
||||
func (o *Overrides) MaxLabelNamesPerSeries(userID string) int { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).MaxLabelNamesPerSeries |
||||
} |
||||
|
||||
// RejectOldSamples returns true when we should reject samples older than certain
|
||||
// age.
|
||||
func (o *Overrides) RejectOldSamples(userID string) bool { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).RejectOldSamples |
||||
} |
||||
|
||||
// RejectOldSamplesMaxAge returns the age at which samples should be rejected.
|
||||
func (o *Overrides) RejectOldSamplesMaxAge(userID string) time.Duration { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).RejectOldSamplesMaxAge |
||||
} |
||||
|
||||
// CreationGracePeriod is misnamed, and actually returns how far into the future
|
||||
// we should accept samples.
|
||||
func (o *Overrides) CreationGracePeriod(userID string) time.Duration { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).CreationGracePeriod |
||||
} |
||||
|
||||
// MaxStreamsPerUser returns the maximum number of streams a user is allowed to store.
|
||||
func (o *Overrides) MaxStreamsPerUser(userID string) int { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).MaxStreamsPerUser |
||||
} |
||||
|
||||
// MaxChunksPerQuery returns the maximum number of chunks allowed per query.
|
||||
func (o *Overrides) MaxChunksPerQuery(userID string) int { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).MaxChunksPerQuery |
||||
} |
||||
|
||||
// MaxQueryLength returns the limit of the length (in time) of a query.
|
||||
func (o *Overrides) MaxQueryLength(userID string) time.Duration { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).MaxQueryLength |
||||
} |
||||
|
||||
// MaxQueryParallelism returns the limit to the number of sub-queries the
|
||||
// frontend will process in parallel.
|
||||
func (o *Overrides) MaxQueryParallelism(userID string) int { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).MaxQueryParallelism |
||||
} |
||||
|
||||
// EnforceMetricName whether to enforce the presence of a metric name.
|
||||
func (o *Overrides) EnforceMetricName(userID string) bool { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).EnforceMetricName |
||||
} |
||||
|
||||
// CardinalityLimit whether to enforce the presence of a metric name.
|
||||
func (o *Overrides) CardinalityLimit(userID string) int { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).CardinalityLimit |
||||
} |
||||
|
||||
// MaxStreamsMatchersPerQuery returns the limit to number of streams matchers per query.
|
||||
func (o *Overrides) MaxStreamsMatchersPerQuery(userID string) int { |
||||
return o.overridesManager.GetLimits(userID).(*Limits).MaxStreamsMatchersPerQuery |
||||
} |
||||
|
||||
// Loads overrides and returns the limits as an interface to store them in OverridesManager.
|
||||
// We need to implement it here since OverridesManager must store type Limits in an interface but
|
||||
// it doesn't know its definition to initialize it.
|
||||
// We could have used yamlv3.Node for this but there is no way to enforce strict decoding due to a bug in it
|
||||
// TODO: Use yamlv3.Node to move this to OverridesManager after https://github.com/go-yaml/yaml/issues/460 is fixed
|
||||
func loadOverrides(filename string) (map[string]interface{}, error) { |
||||
f, err := os.Open(filename) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var overrides struct { |
||||
Overrides map[string]*Limits `yaml:"overrides"` |
||||
} |
||||
|
||||
decoder := yaml.NewDecoder(f) |
||||
decoder.SetStrict(true) |
||||
if err := decoder.Decode(&overrides); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
overridesAsInterface := map[string]interface{}{} |
||||
for userID := range overrides.Overrides { |
||||
overridesAsInterface[userID] = overrides.Overrides[userID] |
||||
} |
||||
|
||||
return overridesAsInterface, nil |
||||
} |
||||
@ -0,0 +1,24 @@ |
||||
package validation |
||||
|
||||
import "github.com/prometheus/client_golang/prometheus" |
||||
|
||||
const ( |
||||
discardReasonLabel = "reason" |
||||
|
||||
// RateLimited is one of the values for the reason to discard samples.
|
||||
// Declared here to avoid duplication in ingester and distributor.
|
||||
RateLimited = "rate_limited" |
||||
) |
||||
|
||||
// DiscardedSamples is a metric of the number of discarded samples, by reason.
|
||||
var DiscardedSamples = prometheus.NewCounterVec( |
||||
prometheus.CounterOpts{ |
||||
Name: "loki_discarded_samples_total", |
||||
Help: "The total number of samples that were discarded.", |
||||
}, |
||||
[]string{discardReasonLabel, "user"}, |
||||
) |
||||
|
||||
func init() { |
||||
prometheus.MustRegister(DiscardedSamples) |
||||
} |
||||
5
vendor/github.com/cortexproject/cortex/pkg/chunk/storage/caching_index_client.go
generated
vendored
5
vendor/github.com/cortexproject/cortex/pkg/chunk/storage/caching_index_client.go
generated
vendored
@ -1,85 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net" |
||||
"strings" |
||||
) |
||||
|
||||
// ARPEntry contains a single row of the columnar data represented in
|
||||
// /proc/net/arp.
|
||||
type ARPEntry struct { |
||||
// IP address
|
||||
IPAddr net.IP |
||||
// MAC address
|
||||
HWAddr net.HardwareAddr |
||||
// Name of the device
|
||||
Device string |
||||
} |
||||
|
||||
// GatherARPEntries retrieves all the ARP entries, parse the relevant columns,
|
||||
// and then return a slice of ARPEntry's.
|
||||
func (fs FS) GatherARPEntries() ([]ARPEntry, error) { |
||||
data, err := ioutil.ReadFile(fs.proc.Path("net/arp")) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error reading arp %s: %s", fs.proc.Path("net/arp"), err) |
||||
} |
||||
|
||||
return parseARPEntries(data) |
||||
} |
||||
|
||||
func parseARPEntries(data []byte) ([]ARPEntry, error) { |
||||
lines := strings.Split(string(data), "\n") |
||||
entries := make([]ARPEntry, 0) |
||||
var err error |
||||
const ( |
||||
expectedDataWidth = 6 |
||||
expectedHeaderWidth = 9 |
||||
) |
||||
for _, line := range lines { |
||||
columns := strings.Fields(line) |
||||
width := len(columns) |
||||
|
||||
if width == expectedHeaderWidth || width == 0 { |
||||
continue |
||||
} else if width == expectedDataWidth { |
||||
entry, err := parseARPEntry(columns) |
||||
if err != nil { |
||||
return []ARPEntry{}, fmt.Errorf("failed to parse ARP entry: %s", err) |
||||
} |
||||
entries = append(entries, entry) |
||||
} else { |
||||
return []ARPEntry{}, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedDataWidth) |
||||
} |
||||
|
||||
} |
||||
|
||||
return entries, err |
||||
} |
||||
|
||||
func parseARPEntry(columns []string) (ARPEntry, error) { |
||||
ip := net.ParseIP(columns[0]) |
||||
mac := net.HardwareAddr(columns[3]) |
||||
|
||||
entry := ARPEntry{ |
||||
IPAddr: ip, |
||||
HWAddr: mac, |
||||
Device: columns[5], |
||||
} |
||||
|
||||
return entry, nil |
||||
} |
||||
@ -1,131 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"github.com/prometheus/procfs/internal/util" |
||||
) |
||||
|
||||
// Crypto holds info parsed from /proc/crypto.
|
||||
type Crypto struct { |
||||
Alignmask *uint64 |
||||
Async bool |
||||
Blocksize *uint64 |
||||
Chunksize *uint64 |
||||
Ctxsize *uint64 |
||||
Digestsize *uint64 |
||||
Driver string |
||||
Geniv string |
||||
Internal string |
||||
Ivsize *uint64 |
||||
Maxauthsize *uint64 |
||||
MaxKeysize *uint64 |
||||
MinKeysize *uint64 |
||||
Module string |
||||
Name string |
||||
Priority *int64 |
||||
Refcnt *int64 |
||||
Seedsize *uint64 |
||||
Selftest string |
||||
Type string |
||||
Walksize *uint64 |
||||
} |
||||
|
||||
// Crypto parses an crypto-file (/proc/crypto) and returns a slice of
|
||||
// structs containing the relevant info. More information available here:
|
||||
// https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html
|
||||
func (fs FS) Crypto() ([]Crypto, error) { |
||||
data, err := ioutil.ReadFile(fs.proc.Path("crypto")) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error parsing crypto %s: %s", fs.proc.Path("crypto"), err) |
||||
} |
||||
crypto, err := parseCrypto(data) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error parsing crypto %s: %s", fs.proc.Path("crypto"), err) |
||||
} |
||||
return crypto, nil |
||||
} |
||||
|
||||
func parseCrypto(cryptoData []byte) ([]Crypto, error) { |
||||
crypto := []Crypto{} |
||||
|
||||
cryptoBlocks := bytes.Split(cryptoData, []byte("\n\n")) |
||||
|
||||
for _, block := range cryptoBlocks { |
||||
var newCryptoElem Crypto |
||||
|
||||
lines := strings.Split(string(block), "\n") |
||||
for _, line := range lines { |
||||
if strings.TrimSpace(line) == "" || line[0] == ' ' { |
||||
continue |
||||
} |
||||
fields := strings.Split(line, ":") |
||||
key := strings.TrimSpace(fields[0]) |
||||
value := strings.TrimSpace(fields[1]) |
||||
vp := util.NewValueParser(value) |
||||
|
||||
switch strings.TrimSpace(key) { |
||||
case "async": |
||||
b, err := strconv.ParseBool(value) |
||||
if err == nil { |
||||
newCryptoElem.Async = b |
||||
} |
||||
case "blocksize": |
||||
newCryptoElem.Blocksize = vp.PUInt64() |
||||
case "chunksize": |
||||
newCryptoElem.Chunksize = vp.PUInt64() |
||||
case "digestsize": |
||||
newCryptoElem.Digestsize = vp.PUInt64() |
||||
case "driver": |
||||
newCryptoElem.Driver = value |
||||
case "geniv": |
||||
newCryptoElem.Geniv = value |
||||
case "internal": |
||||
newCryptoElem.Internal = value |
||||
case "ivsize": |
||||
newCryptoElem.Ivsize = vp.PUInt64() |
||||
case "maxauthsize": |
||||
newCryptoElem.Maxauthsize = vp.PUInt64() |
||||
case "max keysize": |
||||
newCryptoElem.MaxKeysize = vp.PUInt64() |
||||
case "min keysize": |
||||
newCryptoElem.MinKeysize = vp.PUInt64() |
||||
case "module": |
||||
newCryptoElem.Module = value |
||||
case "name": |
||||
newCryptoElem.Name = value |
||||
case "priority": |
||||
newCryptoElem.Priority = vp.PInt64() |
||||
case "refcnt": |
||||
newCryptoElem.Refcnt = vp.PInt64() |
||||
case "seedsize": |
||||
newCryptoElem.Seedsize = vp.PUInt64() |
||||
case "selftest": |
||||
newCryptoElem.Selftest = value |
||||
case "type": |
||||
newCryptoElem.Type = value |
||||
case "walksize": |
||||
newCryptoElem.Walksize = vp.PUInt64() |
||||
} |
||||
} |
||||
crypto = append(crypto, newCryptoElem) |
||||
} |
||||
return crypto, nil |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -1,88 +0,0 @@ |
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util |
||||
|
||||
import ( |
||||
"io/ioutil" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// ParseUint32s parses a slice of strings into a slice of uint32s.
|
||||
func ParseUint32s(ss []string) ([]uint32, error) { |
||||
us := make([]uint32, 0, len(ss)) |
||||
for _, s := range ss { |
||||
u, err := strconv.ParseUint(s, 10, 32) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
us = append(us, uint32(u)) |
||||
} |
||||
|
||||
return us, nil |
||||
} |
||||
|
||||
// ParseUint64s parses a slice of strings into a slice of uint64s.
|
||||
func ParseUint64s(ss []string) ([]uint64, error) { |
||||
us := make([]uint64, 0, len(ss)) |
||||
for _, s := range ss { |
||||
u, err := strconv.ParseUint(s, 10, 64) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
us = append(us, u) |
||||
} |
||||
|
||||
return us, nil |
||||
} |
||||
|
||||
// ParsePInt64s parses a slice of strings into a slice of int64 pointers.
|
||||
func ParsePInt64s(ss []string) ([]*int64, error) { |
||||
us := make([]*int64, 0, len(ss)) |
||||
for _, s := range ss { |
||||
u, err := strconv.ParseInt(s, 10, 64) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
us = append(us, &u) |
||||
} |
||||
|
||||
return us, nil |
||||
} |
||||
|
||||
// ReadUintFromFile reads a file and attempts to parse a uint64 from it.
|
||||
func ReadUintFromFile(path string) (uint64, error) { |
||||
data, err := ioutil.ReadFile(path) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) |
||||
} |
||||
|
||||
// ParseBool parses a string into a boolean pointer.
|
||||
func ParseBool(b string) *bool { |
||||
var truth bool |
||||
switch b { |
||||
case "enabled": |
||||
truth = true |
||||
case "disabled": |
||||
truth = false |
||||
default: |
||||
return nil |
||||
} |
||||
return &truth |
||||
} |
||||
@ -1,45 +0,0 @@ |
||||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux,!appengine
|
||||
|
||||
package util |
||||
|
||||
import ( |
||||
"bytes" |
||||
"os" |
||||
"syscall" |
||||
) |
||||
|
||||
// SysReadFile is a simplified ioutil.ReadFile that invokes syscall.Read directly.
|
||||
// https://github.com/prometheus/node_exporter/pull/728/files
|
||||
func SysReadFile(file string) (string, error) { |
||||
f, err := os.Open(file) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
defer f.Close() |
||||
|
||||
// On some machines, hwmon drivers are broken and return EAGAIN. This causes
|
||||
// Go's ioutil.ReadFile implementation to poll forever.
|
||||
//
|
||||
// Since we either want to read data or bail immediately, do the simplest
|
||||
// possible read using syscall directly.
|
||||
b := make([]byte, 128) |
||||
n, err := syscall.Read(int(f.Fd()), b) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
return string(bytes.TrimSpace(b[:n])), nil |
||||
} |
||||
@ -1,26 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux,appengine !linux
|
||||
|
||||
package util |
||||
|
||||
import ( |
||||
"fmt" |
||||
) |
||||
|
||||
// SysReadFile is here implemented as a noop for builds that do not support
|
||||
// the read syscall. For example Windows, or Linux on Google App Engine.
|
||||
func SysReadFile(file string) (string, error) { |
||||
return "", fmt.Errorf("not supported on this platform") |
||||
} |
||||
@ -1,77 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util |
||||
|
||||
import ( |
||||
"strconv" |
||||
) |
||||
|
||||
// TODO(mdlayher): util packages are an anti-pattern and this should be moved
|
||||
// somewhere else that is more focused in the future.
|
||||
|
||||
// A ValueParser enables parsing a single string into a variety of data types
|
||||
// in a concise and safe way. The Err method must be invoked after invoking
|
||||
// any other methods to ensure a value was successfully parsed.
|
||||
type ValueParser struct { |
||||
v string |
||||
err error |
||||
} |
||||
|
||||
// NewValueParser creates a ValueParser using the input string.
|
||||
func NewValueParser(v string) *ValueParser { |
||||
return &ValueParser{v: v} |
||||
} |
||||
|
||||
// PInt64 interprets the underlying value as an int64 and returns a pointer to
|
||||
// that value.
|
||||
func (vp *ValueParser) PInt64() *int64 { |
||||
if vp.err != nil { |
||||
return nil |
||||
} |
||||
|
||||
// A base value of zero makes ParseInt infer the correct base using the
|
||||
// string's prefix, if any.
|
||||
const base = 0 |
||||
v, err := strconv.ParseInt(vp.v, base, 64) |
||||
if err != nil { |
||||
vp.err = err |
||||
return nil |
||||
} |
||||
|
||||
return &v |
||||
} |
||||
|
||||
// PUInt64 interprets the underlying value as an uint64 and returns a pointer to
|
||||
// that value.
|
||||
func (vp *ValueParser) PUInt64() *uint64 { |
||||
if vp.err != nil { |
||||
return nil |
||||
} |
||||
|
||||
// A base value of zero makes ParseInt infer the correct base using the
|
||||
// string's prefix, if any.
|
||||
const base = 0 |
||||
v, err := strconv.ParseUint(vp.v, base, 64) |
||||
if err != nil { |
||||
vp.err = err |
||||
return nil |
||||
} |
||||
|
||||
return &v |
||||
} |
||||
|
||||
// Err returns the last error, if any, encountered by the ValueParser.
|
||||
func (vp *ValueParser) Err() error { |
||||
return vp.err |
||||
} |
||||
@ -1,91 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/ioutil" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// For the proc file format details,
|
||||
// see https://elixir.bootlin.com/linux/v4.17/source/net/core/net-procfs.c#L162
|
||||
// and https://elixir.bootlin.com/linux/v4.17/source/include/linux/netdevice.h#L2810.
|
||||
|
||||
// SoftnetEntry contains a single row of data from /proc/net/softnet_stat
|
||||
type SoftnetEntry struct { |
||||
// Number of processed packets
|
||||
Processed uint |
||||
// Number of dropped packets
|
||||
Dropped uint |
||||
// Number of times processing packets ran out of quota
|
||||
TimeSqueezed uint |
||||
} |
||||
|
||||
// GatherSoftnetStats reads /proc/net/softnet_stat, parse the relevant columns,
|
||||
// and then return a slice of SoftnetEntry's.
|
||||
func (fs FS) GatherSoftnetStats() ([]SoftnetEntry, error) { |
||||
data, err := ioutil.ReadFile(fs.proc.Path("net/softnet_stat")) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error reading softnet %s: %s", fs.proc.Path("net/softnet_stat"), err) |
||||
} |
||||
|
||||
return parseSoftnetEntries(data) |
||||
} |
||||
|
||||
func parseSoftnetEntries(data []byte) ([]SoftnetEntry, error) { |
||||
lines := strings.Split(string(data), "\n") |
||||
entries := make([]SoftnetEntry, 0) |
||||
var err error |
||||
const ( |
||||
expectedColumns = 11 |
||||
) |
||||
for _, line := range lines { |
||||
columns := strings.Fields(line) |
||||
width := len(columns) |
||||
if width == 0 { |
||||
continue |
||||
} |
||||
if width != expectedColumns { |
||||
return []SoftnetEntry{}, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns) |
||||
} |
||||
var entry SoftnetEntry |
||||
if entry, err = parseSoftnetEntry(columns); err != nil { |
||||
return []SoftnetEntry{}, err |
||||
} |
||||
entries = append(entries, entry) |
||||
} |
||||
|
||||
return entries, nil |
||||
} |
||||
|
||||
func parseSoftnetEntry(columns []string) (SoftnetEntry, error) { |
||||
var err error |
||||
var processed, dropped, timeSqueezed uint64 |
||||
if processed, err = strconv.ParseUint(columns[0], 16, 32); err != nil { |
||||
return SoftnetEntry{}, fmt.Errorf("Unable to parse column 0: %s", err) |
||||
} |
||||
if dropped, err = strconv.ParseUint(columns[1], 16, 32); err != nil { |
||||
return SoftnetEntry{}, fmt.Errorf("Unable to parse column 1: %s", err) |
||||
} |
||||
if timeSqueezed, err = strconv.ParseUint(columns[2], 16, 32); err != nil { |
||||
return SoftnetEntry{}, fmt.Errorf("Unable to parse column 2: %s", err) |
||||
} |
||||
return SoftnetEntry{ |
||||
Processed: uint(processed), |
||||
Dropped: uint(dropped), |
||||
TimeSqueezed: uint(timeSqueezed), |
||||
}, nil |
||||
} |
||||
@ -1,132 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
"regexp" |
||||
"strings" |
||||
) |
||||
|
||||
// Regexp variables
|
||||
var ( |
||||
rPos = regexp.MustCompile(`^pos:\s+(\d+)$`) |
||||
rFlags = regexp.MustCompile(`^flags:\s+(\d+)$`) |
||||
rMntID = regexp.MustCompile(`^mnt_id:\s+(\d+)$`) |
||||
rInotify = regexp.MustCompile(`^inotify`) |
||||
) |
||||
|
||||
// ProcFDInfo contains represents file descriptor information.
|
||||
type ProcFDInfo struct { |
||||
// File descriptor
|
||||
FD string |
||||
// File offset
|
||||
Pos string |
||||
// File access mode and status flags
|
||||
Flags string |
||||
// Mount point ID
|
||||
MntID string |
||||
// List of inotify lines (structed) in the fdinfo file (kernel 3.8+ only)
|
||||
InotifyInfos []InotifyInfo |
||||
} |
||||
|
||||
// FDInfo constructor. On kernels older than 3.8, InotifyInfos will always be empty.
|
||||
func (p Proc) FDInfo(fd string) (*ProcFDInfo, error) { |
||||
f, err := os.Open(p.path("fdinfo", fd)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer f.Close() |
||||
|
||||
fdinfo, err := ioutil.ReadAll(f) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("could not read %s: %s", f.Name(), err) |
||||
} |
||||
|
||||
var text, pos, flags, mntid string |
||||
var inotify []InotifyInfo |
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(fdinfo))) |
||||
for scanner.Scan() { |
||||
text = scanner.Text() |
||||
if rPos.MatchString(text) { |
||||
pos = rPos.FindStringSubmatch(text)[1] |
||||
} else if rFlags.MatchString(text) { |
||||
flags = rFlags.FindStringSubmatch(text)[1] |
||||
} else if rMntID.MatchString(text) { |
||||
mntid = rMntID.FindStringSubmatch(text)[1] |
||||
} else if rInotify.MatchString(text) { |
||||
newInotify, err := parseInotifyInfo(text) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
inotify = append(inotify, *newInotify) |
||||
} |
||||
} |
||||
|
||||
i := &ProcFDInfo{ |
||||
FD: fd, |
||||
Pos: pos, |
||||
Flags: flags, |
||||
MntID: mntid, |
||||
InotifyInfos: inotify, |
||||
} |
||||
|
||||
return i, nil |
||||
} |
||||
|
||||
// InotifyInfo represents a single inotify line in the fdinfo file.
|
||||
type InotifyInfo struct { |
||||
// Watch descriptor number
|
||||
WD string |
||||
// Inode number
|
||||
Ino string |
||||
// Device ID
|
||||
Sdev string |
||||
// Mask of events being monitored
|
||||
Mask string |
||||
} |
||||
|
||||
// InotifyInfo constructor. Only available on kernel 3.8+.
|
||||
func parseInotifyInfo(line string) (*InotifyInfo, error) { |
||||
r := regexp.MustCompile(`^inotify\s+wd:([0-9a-f]+)\s+ino:([0-9a-f]+)\s+sdev:([0-9a-f]+)\s+mask:([0-9a-f]+)`) |
||||
m := r.FindStringSubmatch(line) |
||||
i := &InotifyInfo{ |
||||
WD: m[1], |
||||
Ino: m[2], |
||||
Sdev: m[3], |
||||
Mask: m[4], |
||||
} |
||||
return i, nil |
||||
} |
||||
|
||||
// ProcFDInfos represents a list of ProcFDInfo structs.
|
||||
type ProcFDInfos []ProcFDInfo |
||||
|
||||
func (p ProcFDInfos) Len() int { return len(p) } |
||||
func (p ProcFDInfos) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |
||||
func (p ProcFDInfos) Less(i, j int) bool { return p[i].FD < p[j].FD } |
||||
|
||||
// InotifyWatchLen returns the total number of inotify watches
|
||||
func (p ProcFDInfos) InotifyWatchLen() (int, error) { |
||||
length := 0 |
||||
for _, f := range p { |
||||
length += len(f.InotifyInfos) |
||||
} |
||||
|
||||
return length, nil |
||||
} |
||||
@ -1,118 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs |
||||
|
||||
import ( |
||||
"bufio" |
||||
"errors" |
||||
"os" |
||||
"regexp" |
||||
"strconv" |
||||
) |
||||
|
||||
var ( |
||||
cpuLineRE = regexp.MustCompile(`cpu(\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)`) |
||||
procLineRE = regexp.MustCompile(`(\d+) (\d+) (\d+)`) |
||||
) |
||||
|
||||
// Schedstat contains scheduler statistics from /proc/schedstats
|
||||
//
|
||||
// See
|
||||
// https://www.kernel.org/doc/Documentation/scheduler/sched-stats.txt
|
||||
// for a detailed description of what these numbers mean.
|
||||
//
|
||||
// Note the current kernel documentation claims some of the time units are in
|
||||
// jiffies when they are actually in nanoseconds since 2.6.23 with the
|
||||
// introduction of CFS. A fix to the documentation is pending. See
|
||||
// https://lore.kernel.org/patchwork/project/lkml/list/?series=403473
|
||||
|
||||
type Schedstat struct { |
||||
CPUs []*SchedstatCPU |
||||
} |
||||
|
||||
// SchedstatCPU contains the values from one "cpu<N>" line
|
||||
type SchedstatCPU struct { |
||||
CPUNum string |
||||
|
||||
RunningNanoseconds uint64 |
||||
WaitingNanoseconds uint64 |
||||
RunTimeslices uint64 |
||||
} |
||||
|
||||
// ProcSchedstat contains the values from /proc/<pid>/schedstat
|
||||
type ProcSchedstat struct { |
||||
RunningNanoseconds uint64 |
||||
WaitingNanoseconds uint64 |
||||
RunTimeslices uint64 |
||||
} |
||||
|
||||
func (fs FS) Schedstat() (*Schedstat, error) { |
||||
file, err := os.Open(fs.proc.Path("schedstat")) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer file.Close() |
||||
|
||||
stats := &Schedstat{} |
||||
scanner := bufio.NewScanner(file) |
||||
|
||||
for scanner.Scan() { |
||||
match := cpuLineRE.FindStringSubmatch(scanner.Text()) |
||||
if match != nil { |
||||
cpu := &SchedstatCPU{} |
||||
cpu.CPUNum = match[1] |
||||
|
||||
cpu.RunningNanoseconds, err = strconv.ParseUint(match[8], 10, 64) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
|
||||
cpu.WaitingNanoseconds, err = strconv.ParseUint(match[9], 10, 64) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
|
||||
cpu.RunTimeslices, err = strconv.ParseUint(match[10], 10, 64) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
|
||||
stats.CPUs = append(stats.CPUs, cpu) |
||||
} |
||||
} |
||||
|
||||
return stats, nil |
||||
} |
||||
|
||||
func parseProcSchedstat(contents string) (stats ProcSchedstat, err error) { |
||||
match := procLineRE.FindStringSubmatch(contents) |
||||
|
||||
if match != nil { |
||||
stats.RunningNanoseconds, err = strconv.ParseUint(match[1], 10, 64) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
stats.WaitingNanoseconds, err = strconv.ParseUint(match[2], 10, 64) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
stats.RunTimeslices, err = strconv.ParseUint(match[3], 10, 64) |
||||
return |
||||
} |
||||
|
||||
err = errors.New("could not parse schedstat") |
||||
return |
||||
} |
||||
@ -1,210 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package procfs |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"github.com/prometheus/procfs/internal/util" |
||||
) |
||||
|
||||
// The VM interface is described at
|
||||
// https://www.kernel.org/doc/Documentation/sysctl/vm.txt
|
||||
// Each setting is exposed as a single file.
|
||||
// Each file contains one line with a single numerical value, except lowmem_reserve_ratio which holds an array
|
||||
// and numa_zonelist_order (deprecated) which is a string
|
||||
type VM struct { |
||||
AdminReserveKbytes *int64 // /proc/sys/vm/admin_reserve_kbytes
|
||||
BlockDump *int64 // /proc/sys/vm/block_dump
|
||||
CompactUnevictableAllowed *int64 // /proc/sys/vm/compact_unevictable_allowed
|
||||
DirtyBackgroundBytes *int64 // /proc/sys/vm/dirty_background_bytes
|
||||
DirtyBackgroundRatio *int64 // /proc/sys/vm/dirty_background_ratio
|
||||
DirtyBytes *int64 // /proc/sys/vm/dirty_bytes
|
||||
DirtyExpireCentisecs *int64 // /proc/sys/vm/dirty_expire_centisecs
|
||||
DirtyRatio *int64 // /proc/sys/vm/dirty_ratio
|
||||
DirtytimeExpireSeconds *int64 // /proc/sys/vm/dirtytime_expire_seconds
|
||||
DirtyWritebackCentisecs *int64 // /proc/sys/vm/dirty_writeback_centisecs
|
||||
DropCaches *int64 // /proc/sys/vm/drop_caches
|
||||
ExtfragThreshold *int64 // /proc/sys/vm/extfrag_threshold
|
||||
HugetlbShmGroup *int64 // /proc/sys/vm/hugetlb_shm_group
|
||||
LaptopMode *int64 // /proc/sys/vm/laptop_mode
|
||||
LegacyVaLayout *int64 // /proc/sys/vm/legacy_va_layout
|
||||
LowmemReserveRatio []*int64 // /proc/sys/vm/lowmem_reserve_ratio
|
||||
MaxMapCount *int64 // /proc/sys/vm/max_map_count
|
||||
MemoryFailureEarlyKill *int64 // /proc/sys/vm/memory_failure_early_kill
|
||||
MemoryFailureRecovery *int64 // /proc/sys/vm/memory_failure_recovery
|
||||
MinFreeKbytes *int64 // /proc/sys/vm/min_free_kbytes
|
||||
MinSlabRatio *int64 // /proc/sys/vm/min_slab_ratio
|
||||
MinUnmappedRatio *int64 // /proc/sys/vm/min_unmapped_ratio
|
||||
MmapMinAddr *int64 // /proc/sys/vm/mmap_min_addr
|
||||
NrHugepages *int64 // /proc/sys/vm/nr_hugepages
|
||||
NrHugepagesMempolicy *int64 // /proc/sys/vm/nr_hugepages_mempolicy
|
||||
NrOvercommitHugepages *int64 // /proc/sys/vm/nr_overcommit_hugepages
|
||||
NumaStat *int64 // /proc/sys/vm/numa_stat
|
||||
NumaZonelistOrder string // /proc/sys/vm/numa_zonelist_order
|
||||
OomDumpTasks *int64 // /proc/sys/vm/oom_dump_tasks
|
||||
OomKillAllocatingTask *int64 // /proc/sys/vm/oom_kill_allocating_task
|
||||
OvercommitKbytes *int64 // /proc/sys/vm/overcommit_kbytes
|
||||
OvercommitMemory *int64 // /proc/sys/vm/overcommit_memory
|
||||
OvercommitRatio *int64 // /proc/sys/vm/overcommit_ratio
|
||||
PageCluster *int64 // /proc/sys/vm/page-cluster
|
||||
PanicOnOom *int64 // /proc/sys/vm/panic_on_oom
|
||||
PercpuPagelistFraction *int64 // /proc/sys/vm/percpu_pagelist_fraction
|
||||
StatInterval *int64 // /proc/sys/vm/stat_interval
|
||||
Swappiness *int64 // /proc/sys/vm/swappiness
|
||||
UserReserveKbytes *int64 // /proc/sys/vm/user_reserve_kbytes
|
||||
VfsCachePressure *int64 // /proc/sys/vm/vfs_cache_pressure
|
||||
WatermarkBoostFactor *int64 // /proc/sys/vm/watermark_boost_factor
|
||||
WatermarkScaleFactor *int64 // /proc/sys/vm/watermark_scale_factor
|
||||
ZoneReclaimMode *int64 // /proc/sys/vm/zone_reclaim_mode
|
||||
} |
||||
|
||||
// VM reads the VM statistics from the specified `proc` filesystem.
|
||||
func (fs FS) VM() (*VM, error) { |
||||
path := fs.proc.Path("sys/vm") |
||||
file, err := os.Stat(path) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if !file.Mode().IsDir() { |
||||
return nil, fmt.Errorf("%s is not a directory", path) |
||||
} |
||||
|
||||
files, err := ioutil.ReadDir(path) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var vm VM |
||||
for _, f := range files { |
||||
if f.IsDir() { |
||||
continue |
||||
} |
||||
|
||||
name := filepath.Join(path, f.Name()) |
||||
// ignore errors on read, as there are some write only
|
||||
// in /proc/sys/vm
|
||||
value, err := util.SysReadFile(name) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
vp := util.NewValueParser(value) |
||||
|
||||
switch f.Name() { |
||||
case "admin_reserve_kbytes": |
||||
vm.AdminReserveKbytes = vp.PInt64() |
||||
case "block_dump": |
||||
vm.BlockDump = vp.PInt64() |
||||
case "compact_unevictable_allowed": |
||||
vm.CompactUnevictableAllowed = vp.PInt64() |
||||
case "dirty_background_bytes": |
||||
vm.DirtyBackgroundBytes = vp.PInt64() |
||||
case "dirty_background_ratio": |
||||
vm.DirtyBackgroundRatio = vp.PInt64() |
||||
case "dirty_bytes": |
||||
vm.DirtyBytes = vp.PInt64() |
||||
case "dirty_expire_centisecs": |
||||
vm.DirtyExpireCentisecs = vp.PInt64() |
||||
case "dirty_ratio": |
||||
vm.DirtyRatio = vp.PInt64() |
||||
case "dirtytime_expire_seconds": |
||||
vm.DirtytimeExpireSeconds = vp.PInt64() |
||||
case "dirty_writeback_centisecs": |
||||
vm.DirtyWritebackCentisecs = vp.PInt64() |
||||
case "drop_caches": |
||||
vm.DropCaches = vp.PInt64() |
||||
case "extfrag_threshold": |
||||
vm.ExtfragThreshold = vp.PInt64() |
||||
case "hugetlb_shm_group": |
||||
vm.HugetlbShmGroup = vp.PInt64() |
||||
case "laptop_mode": |
||||
vm.LaptopMode = vp.PInt64() |
||||
case "legacy_va_layout": |
||||
vm.LegacyVaLayout = vp.PInt64() |
||||
case "lowmem_reserve_ratio": |
||||
stringSlice := strings.Fields(value) |
||||
pint64Slice := make([]*int64, 0, len(stringSlice)) |
||||
for _, value := range stringSlice { |
||||
vp := util.NewValueParser(value) |
||||
pint64Slice = append(pint64Slice, vp.PInt64()) |
||||
} |
||||
vm.LowmemReserveRatio = pint64Slice |
||||
case "max_map_count": |
||||
vm.MaxMapCount = vp.PInt64() |
||||
case "memory_failure_early_kill": |
||||
vm.MemoryFailureEarlyKill = vp.PInt64() |
||||
case "memory_failure_recovery": |
||||
vm.MemoryFailureRecovery = vp.PInt64() |
||||
case "min_free_kbytes": |
||||
vm.MinFreeKbytes = vp.PInt64() |
||||
case "min_slab_ratio": |
||||
vm.MinSlabRatio = vp.PInt64() |
||||
case "min_unmapped_ratio": |
||||
vm.MinUnmappedRatio = vp.PInt64() |
||||
case "mmap_min_addr": |
||||
vm.MmapMinAddr = vp.PInt64() |
||||
case "nr_hugepages": |
||||
vm.NrHugepages = vp.PInt64() |
||||
case "nr_hugepages_mempolicy": |
||||
vm.NrHugepagesMempolicy = vp.PInt64() |
||||
case "nr_overcommit_hugepages": |
||||
vm.NrOvercommitHugepages = vp.PInt64() |
||||
case "numa_stat": |
||||
vm.NumaStat = vp.PInt64() |
||||
case "numa_zonelist_order": |
||||
vm.NumaZonelistOrder = value |
||||
case "oom_dump_tasks": |
||||
vm.OomDumpTasks = vp.PInt64() |
||||
case "oom_kill_allocating_task": |
||||
vm.OomKillAllocatingTask = vp.PInt64() |
||||
case "overcommit_kbytes": |
||||
vm.OvercommitKbytes = vp.PInt64() |
||||
case "overcommit_memory": |
||||
vm.OvercommitMemory = vp.PInt64() |
||||
case "overcommit_ratio": |
||||
vm.OvercommitRatio = vp.PInt64() |
||||
case "page-cluster": |
||||
vm.PageCluster = vp.PInt64() |
||||
case "panic_on_oom": |
||||
vm.PanicOnOom = vp.PInt64() |
||||
case "percpu_pagelist_fraction": |
||||
vm.PercpuPagelistFraction = vp.PInt64() |
||||
case "stat_interval": |
||||
vm.StatInterval = vp.PInt64() |
||||
case "swappiness": |
||||
vm.Swappiness = vp.PInt64() |
||||
case "user_reserve_kbytes": |
||||
vm.UserReserveKbytes = vp.PInt64() |
||||
case "vfs_cache_pressure": |
||||
vm.VfsCachePressure = vp.PInt64() |
||||
case "watermark_boost_factor": |
||||
vm.WatermarkBoostFactor = vp.PInt64() |
||||
case "watermark_scale_factor": |
||||
vm.WatermarkScaleFactor = vp.PInt64() |
||||
case "zone_reclaim_mode": |
||||
vm.ZoneReclaimMode = vp.PInt64() |
||||
} |
||||
if err := vp.Err(); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
return &vm, nil |
||||
} |
||||
@ -1,196 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package procfs |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"regexp" |
||||
"strings" |
||||
|
||||
"github.com/prometheus/procfs/internal/util" |
||||
) |
||||
|
||||
// Zoneinfo holds info parsed from /proc/zoneinfo.
|
||||
type Zoneinfo struct { |
||||
Node string |
||||
Zone string |
||||
NrFreePages *int64 |
||||
Min *int64 |
||||
Low *int64 |
||||
High *int64 |
||||
Scanned *int64 |
||||
Spanned *int64 |
||||
Present *int64 |
||||
Managed *int64 |
||||
NrActiveAnon *int64 |
||||
NrInactiveAnon *int64 |
||||
NrIsolatedAnon *int64 |
||||
NrAnonPages *int64 |
||||
NrAnonTransparentHugepages *int64 |
||||
NrActiveFile *int64 |
||||
NrInactiveFile *int64 |
||||
NrIsolatedFile *int64 |
||||
NrFilePages *int64 |
||||
NrSlabReclaimable *int64 |
||||
NrSlabUnreclaimable *int64 |
||||
NrMlockStack *int64 |
||||
NrKernelStack *int64 |
||||
NrMapped *int64 |
||||
NrDirty *int64 |
||||
NrWriteback *int64 |
||||
NrUnevictable *int64 |
||||
NrShmem *int64 |
||||
NrDirtied *int64 |
||||
NrWritten *int64 |
||||
NumaHit *int64 |
||||
NumaMiss *int64 |
||||
NumaForeign *int64 |
||||
NumaInterleave *int64 |
||||
NumaLocal *int64 |
||||
NumaOther *int64 |
||||
Protection []*int64 |
||||
} |
||||
|
||||
var nodeZoneRE = regexp.MustCompile(`(\d+), zone\s+(\w+)`) |
||||
|
||||
// Zoneinfo parses an zoneinfo-file (/proc/zoneinfo) and returns a slice of
|
||||
// structs containing the relevant info. More information available here:
|
||||
// https://www.kernel.org/doc/Documentation/sysctl/vm.txt
|
||||
func (fs FS) Zoneinfo() ([]Zoneinfo, error) { |
||||
data, err := ioutil.ReadFile(fs.proc.Path("zoneinfo")) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error reading zoneinfo %s: %s", fs.proc.Path("zoneinfo"), err) |
||||
} |
||||
zoneinfo, err := parseZoneinfo(data) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error parsing zoneinfo %s: %s", fs.proc.Path("zoneinfo"), err) |
||||
} |
||||
return zoneinfo, nil |
||||
} |
||||
|
||||
func parseZoneinfo(zoneinfoData []byte) ([]Zoneinfo, error) { |
||||
|
||||
zoneinfo := []Zoneinfo{} |
||||
|
||||
zoneinfoBlocks := bytes.Split(zoneinfoData, []byte("\nNode")) |
||||
for _, block := range zoneinfoBlocks { |
||||
var zoneinfoElement Zoneinfo |
||||
lines := strings.Split(string(block), "\n") |
||||
for _, line := range lines { |
||||
|
||||
if nodeZone := nodeZoneRE.FindStringSubmatch(line); nodeZone != nil { |
||||
zoneinfoElement.Node = nodeZone[1] |
||||
zoneinfoElement.Zone = nodeZone[2] |
||||
continue |
||||
} |
||||
if strings.HasPrefix(strings.TrimSpace(line), "per-node stats") { |
||||
zoneinfoElement.Zone = "" |
||||
continue |
||||
} |
||||
parts := strings.Fields(strings.TrimSpace(line)) |
||||
if len(parts) < 2 { |
||||
continue |
||||
} |
||||
vp := util.NewValueParser(parts[1]) |
||||
switch parts[0] { |
||||
case "nr_free_pages": |
||||
zoneinfoElement.NrFreePages = vp.PInt64() |
||||
case "min": |
||||
zoneinfoElement.Min = vp.PInt64() |
||||
case "low": |
||||
zoneinfoElement.Low = vp.PInt64() |
||||
case "high": |
||||
zoneinfoElement.High = vp.PInt64() |
||||
case "scanned": |
||||
zoneinfoElement.Scanned = vp.PInt64() |
||||
case "spanned": |
||||
zoneinfoElement.Spanned = vp.PInt64() |
||||
case "present": |
||||
zoneinfoElement.Present = vp.PInt64() |
||||
case "managed": |
||||
zoneinfoElement.Managed = vp.PInt64() |
||||
case "nr_active_anon": |
||||
zoneinfoElement.NrActiveAnon = vp.PInt64() |
||||
case "nr_inactive_anon": |
||||
zoneinfoElement.NrInactiveAnon = vp.PInt64() |
||||
case "nr_isolated_anon": |
||||
zoneinfoElement.NrIsolatedAnon = vp.PInt64() |
||||
case "nr_anon_pages": |
||||
zoneinfoElement.NrAnonPages = vp.PInt64() |
||||
case "nr_anon_transparent_hugepages": |
||||
zoneinfoElement.NrAnonTransparentHugepages = vp.PInt64() |
||||
case "nr_active_file": |
||||
zoneinfoElement.NrActiveFile = vp.PInt64() |
||||
case "nr_inactive_file": |
||||
zoneinfoElement.NrInactiveFile = vp.PInt64() |
||||
case "nr_isolated_file": |
||||
zoneinfoElement.NrIsolatedFile = vp.PInt64() |
||||
case "nr_file_pages": |
||||
zoneinfoElement.NrFilePages = vp.PInt64() |
||||
case "nr_slab_reclaimable": |
||||
zoneinfoElement.NrSlabReclaimable = vp.PInt64() |
||||
case "nr_slab_unreclaimable": |
||||
zoneinfoElement.NrSlabUnreclaimable = vp.PInt64() |
||||
case "nr_mlock_stack": |
||||
zoneinfoElement.NrMlockStack = vp.PInt64() |
||||
case "nr_kernel_stack": |
||||
zoneinfoElement.NrKernelStack = vp.PInt64() |
||||
case "nr_mapped": |
||||
zoneinfoElement.NrMapped = vp.PInt64() |
||||
case "nr_dirty": |
||||
zoneinfoElement.NrDirty = vp.PInt64() |
||||
case "nr_writeback": |
||||
zoneinfoElement.NrWriteback = vp.PInt64() |
||||
case "nr_unevictable": |
||||
zoneinfoElement.NrUnevictable = vp.PInt64() |
||||
case "nr_shmem": |
||||
zoneinfoElement.NrShmem = vp.PInt64() |
||||
case "nr_dirtied": |
||||
zoneinfoElement.NrDirtied = vp.PInt64() |
||||
case "nr_written": |
||||
zoneinfoElement.NrWritten = vp.PInt64() |
||||
case "numa_hit": |
||||
zoneinfoElement.NumaHit = vp.PInt64() |
||||
case "numa_miss": |
||||
zoneinfoElement.NumaMiss = vp.PInt64() |
||||
case "numa_foreign": |
||||
zoneinfoElement.NumaForeign = vp.PInt64() |
||||
case "numa_interleave": |
||||
zoneinfoElement.NumaInterleave = vp.PInt64() |
||||
case "numa_local": |
||||
zoneinfoElement.NumaLocal = vp.PInt64() |
||||
case "numa_other": |
||||
zoneinfoElement.NumaOther = vp.PInt64() |
||||
case "protection:": |
||||
protectionParts := strings.Split(line, ":") |
||||
protectionValues := strings.Replace(protectionParts[1], "(", "", 1) |
||||
protectionValues = strings.Replace(protectionValues, ")", "", 1) |
||||
protectionValues = strings.TrimSpace(protectionValues) |
||||
protectionStringMap := strings.Split(protectionValues, ", ") |
||||
val, err := util.ParsePInt64s(protectionStringMap) |
||||
if err == nil { |
||||
zoneinfoElement.Protection = val |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
zoneinfo = append(zoneinfo, zoneinfoElement) |
||||
} |
||||
return zoneinfo, nil |
||||
} |
||||
Loading…
Reference in new issue