reva-bump-2.39.2. update opencloud 4.0.0-rc.1
This commit is contained in:
+1
-1
@@ -41,6 +41,6 @@ const (
|
||||
)
|
||||
|
||||
// Print prints a message to the local systemd journal using Send().
|
||||
func Print(priority Priority, format string, a ...interface{}) error {
|
||||
func Print(priority Priority, format string, a ...any) error {
|
||||
return Send(fmt.Sprintf(format, a...), priority, nil)
|
||||
}
|
||||
|
||||
+3
-5
@@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
// Package journal provides write bindings to the local systemd journal.
|
||||
// It is implemented in pure Go and connects to the journal directly over its
|
||||
@@ -31,7 +30,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -194,7 +192,7 @@ func appendVariable(w io.Writer, name, value string) {
|
||||
* - the data, followed by a newline
|
||||
*/
|
||||
fmt.Fprintln(w, name)
|
||||
binary.Write(w, binary.LittleEndian, uint64(len(value)))
|
||||
_ = binary.Write(w, binary.LittleEndian, uint64(len(value)))
|
||||
fmt.Fprintln(w, value)
|
||||
} else {
|
||||
/* just write the variable and value all on one line */
|
||||
@@ -214,7 +212,7 @@ func validVarName(name string) error {
|
||||
}
|
||||
|
||||
for _, c := range name {
|
||||
if !(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_') {
|
||||
if ('A' > c || c > 'Z') && ('0' > c || c > '9') && c != '_' {
|
||||
return errors.New("Variable name contains invalid characters")
|
||||
}
|
||||
}
|
||||
@@ -239,7 +237,7 @@ func isSocketSpaceError(err error) bool {
|
||||
|
||||
// tempFd creates a temporary, unlinked file under `/dev/shm`.
|
||||
func tempFd() (*os.File, error) {
|
||||
file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
|
||||
file, err := os.CreateTemp("/dev/shm/", "journal.XXXXX")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Generated
Vendored
+10
-3
@@ -1733,16 +1733,23 @@ func hasPreview(md *provider.ResourceInfo, appendToOK func(p ...prop.PropertyXML
|
||||
}
|
||||
|
||||
func downloadURL(ctx context.Context, log zerolog.Logger, isPublic bool, path string, ls *link.PublicShare, publicURL string, baseURI string, urlSigner signedurl.Signer) string {
|
||||
parts := strings.Split(path, "/")
|
||||
encodedPath, err := url.JoinPath("/", parts...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to encode the path for the download URL")
|
||||
return ""
|
||||
}
|
||||
|
||||
switch {
|
||||
case isPublic:
|
||||
var queryString string
|
||||
if !ls.PasswordProtected {
|
||||
queryString = path
|
||||
queryString = encodedPath
|
||||
} else {
|
||||
expiration := time.Unix(int64(ls.Signature.SignatureExpiration.Seconds), int64(ls.Signature.SignatureExpiration.Nanos))
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(path)
|
||||
sb.WriteString(encodedPath)
|
||||
sb.WriteString("?signature=")
|
||||
sb.WriteString(ls.Signature.Signature)
|
||||
sb.WriteString("&expiration=")
|
||||
@@ -1757,7 +1764,7 @@ func downloadURL(ctx context.Context, log zerolog.Logger, isPublic bool, path st
|
||||
log.Error().Msg("could not get user from context for download URL signing")
|
||||
return ""
|
||||
}
|
||||
signedURL, err := urlSigner.Sign(publicURL+baseURI+path, u.Id.OpaqueId, 30*time.Minute)
|
||||
signedURL, err := urlSigner.Sign(publicURL+baseURI+encodedPath, u.Id.OpaqueId, 30*time.Minute)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to sign download URL")
|
||||
return ""
|
||||
|
||||
+156
-9
@@ -21,9 +21,11 @@ package trashbin
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -165,7 +167,7 @@ func (tb *Trashbin) MoveToTrash(ctx context.Context, n *node.Node, path string)
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. "Forget" the node
|
||||
// 1. "Forget" the node and its children
|
||||
if err = tb.lu.IDCache.DeleteByPath(ctx, path); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -327,7 +329,6 @@ func (tb *Trashbin) RestoreRecycleItem(ctx context.Context, spaceID string, key,
|
||||
}
|
||||
if id == "" {
|
||||
return nil, errtypes.NotFound("trashbin: item not found")
|
||||
|
||||
}
|
||||
|
||||
// update parent id in case it was restored to a different location
|
||||
@@ -370,35 +371,181 @@ func (tb *Trashbin) RestoreRecycleItem(ctx context.Context, spaceID string, key,
|
||||
|
||||
}
|
||||
|
||||
// PurgeRecycleItem purges the specified item, all its children and all their revisions
|
||||
// PurgeRecycleItem purges the specified item, all its children and all their revisions.
|
||||
func (tb *Trashbin) PurgeRecycleItem(ctx context.Context, spaceID, key, relativePath string) error {
|
||||
_, span := tracer.Start(ctx, "PurgeRecycleItem")
|
||||
defer span.End()
|
||||
|
||||
trashRoot := filepath.Join(tb.lu.InternalPath(spaceID, spaceID), ".Trash")
|
||||
err := os.RemoveAll(filepath.Clean(filepath.Join(trashRoot, "files", key+".trashitem", relativePath)))
|
||||
if err != nil {
|
||||
trashPath := filepath.Clean(filepath.Join(trashRoot, "files", key+".trashitem", relativePath))
|
||||
|
||||
type item struct {
|
||||
path string
|
||||
isDir bool
|
||||
}
|
||||
|
||||
itemChan := make(chan item, 256) // small buffer to smooth bursts
|
||||
var dirs []string
|
||||
|
||||
// Start walking the directory tree in a separate goroutine
|
||||
walkErrChan := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(itemChan)
|
||||
defer close(walkErrChan)
|
||||
|
||||
err := filepath.WalkDir(trashPath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
it := item{path: path, isDir: d.IsDir()}
|
||||
|
||||
// Directories are collected for later filesystem removal
|
||||
if d.IsDir() {
|
||||
dirs = append(dirs, path)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case itemChan <- it:
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
walkErrChan <- err
|
||||
return
|
||||
}
|
||||
walkErrChan <- nil
|
||||
}()
|
||||
|
||||
// Start worker pool for metadata purge
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < tb.o.MaxConcurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func(ch <-chan item) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
tb.log.Info().Msg("context cancelled during purge")
|
||||
return
|
||||
case it, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
_, id, _, _, err := tb.lu.MetadataBackend().IdentifyPath(ctx, it.path)
|
||||
if err == nil && id != "" {
|
||||
trashedNode := &trashNode{spaceID: spaceID, id: id, path: it.path}
|
||||
if err := tb.lu.MetadataBackend().Purge(ctx, trashedNode); err != nil {
|
||||
tb.log.Error().Err(err).Str("path", it.path).Str("id", id).Msg("Failed to purge metadata")
|
||||
}
|
||||
}
|
||||
|
||||
// Delete only files here (directories are deleted later)
|
||||
if !it.isDir {
|
||||
if err := os.Remove(it.path); err != nil && !os.IsNotExist(err) {
|
||||
tb.log.Error().Err(err).Str("path", it.path).Msg("Failed to delete file")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}(itemChan)
|
||||
}
|
||||
|
||||
// Wait for all workers and walker to finish
|
||||
wg.Wait()
|
||||
if err := <-walkErrChan; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete directories in reverse order (leafs first)
|
||||
for i := len(dirs) - 1; i >= 0; i-- {
|
||||
if err := os.Remove(dirs[i]); err != nil && !os.IsNotExist(err) {
|
||||
tb.log.Error().Err(err).Str("path", dirs[i]).Msg("Failed to delete directory")
|
||||
}
|
||||
}
|
||||
|
||||
// Delete trashinfo if purging the root item
|
||||
cleanPath := filepath.Clean(relativePath)
|
||||
if cleanPath == "." || cleanPath == "/" {
|
||||
return os.Remove(filepath.Join(trashRoot, "info", key+".trashinfo"))
|
||||
infoPath := filepath.Join(trashRoot, "info", key+".trashinfo")
|
||||
if err := os.Remove(infoPath); err != nil && !os.IsNotExist(err) {
|
||||
tb.log.Error().Err(err).Str("path", infoPath).Msg("Failed to delete trashinfo")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmptyRecycle empties the trash
|
||||
// EmptyRecycle empties the trash for a given space.
|
||||
func (tb *Trashbin) EmptyRecycle(ctx context.Context, spaceID string) error {
|
||||
_, span := tracer.Start(ctx, "EmptyRecycle")
|
||||
defer span.End()
|
||||
|
||||
trashRoot := filepath.Join(tb.lu.InternalPath(spaceID, spaceID), ".Trash")
|
||||
err := os.RemoveAll(filepath.Clean(filepath.Join(trashRoot, "files")))
|
||||
filesRoot := filepath.Join(trashRoot, "files")
|
||||
|
||||
entries, err := os.ReadDir(filesRoot)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return os.RemoveAll(filepath.Clean(filepath.Join(trashRoot, "info")))
|
||||
|
||||
type job struct {
|
||||
key string
|
||||
}
|
||||
|
||||
jobCh := make(chan job, len(entries))
|
||||
|
||||
// Enqueue all trash items
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if !strings.HasSuffix(name, ".trashitem") {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.TrimSuffix(name, ".trashitem")
|
||||
jobCh <- job{key: key}
|
||||
}
|
||||
close(jobCh)
|
||||
|
||||
// Start worker pool
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < tb.o.MaxConcurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func(ch <-chan job) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
tb.log.Info().Msg("context cancelled during EmptyRecycle")
|
||||
return
|
||||
case j, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err := tb.PurgeRecycleItem(ctx, spaceID, j.key, "."); err != nil {
|
||||
tb.log.Error().Err(err).Str("key", j.key).Msg("Failed to purge trash item")
|
||||
}
|
||||
}
|
||||
}
|
||||
}(jobCh)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tb *Trashbin) IsEmpty(ctx context.Context, spaceID string) bool {
|
||||
|
||||
Generated
Vendored
+5
-5
@@ -467,6 +467,9 @@ func (b HybridBackend) Purge(ctx context.Context, n MetadataNode) error {
|
||||
}
|
||||
}
|
||||
|
||||
// delete the metadata lockfile
|
||||
_ = os.Remove(b.LockfilePath(n))
|
||||
|
||||
return b.metaCache.RemoveMetadata(b.cacheKey(n))
|
||||
}
|
||||
|
||||
@@ -517,11 +520,8 @@ func (b HybridBackend) Lock(n MetadataNode) (UnlockFunc, error) {
|
||||
}
|
||||
}
|
||||
return func() error {
|
||||
err := mlock.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Remove(metaLockPath)
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
return mlock.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
+12
-5
@@ -284,6 +284,16 @@ func (b MessagePackBackend) Purge(_ context.Context, n MetadataNode) error {
|
||||
if err := b.metaCache.RemoveMetadata(b.cacheKey(n)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
internalPath := n.InternalPath()
|
||||
// for trash files always use the path without the timestamp
|
||||
parts := strings.SplitN(n.GetID(), ".T.", 2)
|
||||
if len(parts) > 1 {
|
||||
internalPath = strings.TrimSuffix(internalPath, ".T."+parts[1])
|
||||
}
|
||||
|
||||
_ = os.Remove(internalPath + ".mlock")
|
||||
|
||||
return os.Remove(b.MetadataPath(n))
|
||||
}
|
||||
|
||||
@@ -319,11 +329,8 @@ func (b MessagePackBackend) Lock(n MetadataNode) (UnlockFunc, error) {
|
||||
return nil, err
|
||||
}
|
||||
return func() error {
|
||||
err := mlock.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Remove(metaLockPath)
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
return mlock.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
+6
-6
@@ -248,6 +248,9 @@ func (b XattrsBackend) Purge(ctx context.Context, n MetadataNode) error {
|
||||
}
|
||||
}
|
||||
|
||||
// delete the metadata lockfile
|
||||
_ = os.Remove(b.LockfilePath(n))
|
||||
|
||||
return b.metaCache.RemoveMetadata(b.cacheKey(n))
|
||||
}
|
||||
|
||||
@@ -278,17 +281,14 @@ func (b XattrsBackend) Lock(n MetadataNode) (UnlockFunc, error) {
|
||||
return nil, err
|
||||
}
|
||||
return func() error {
|
||||
err := mlock.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Remove(metaLockPath)
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
return mlock.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func cleanupLockfile(_ context.Context, f *lockedfile.File) {
|
||||
_ = f.Close()
|
||||
_ = os.Remove(f.Name())
|
||||
// Warning: do not remove the lockfile or we may lock the same file more than once, https://github.com/opencloud-eu/opencloud/issues/1793
|
||||
}
|
||||
|
||||
// AllWithLockedSource reads all extended attributes from the given reader.
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ func NewFlags(logger *slog.Logger, features string) (Flagger, error) {
|
||||
opts = append(opts, enableAutoGOMAXPROCS())
|
||||
logger.Warn("Automatically set GOMAXPROCS to match Linux container CPU quota")
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown option '%s' for --enable-feature", feature)
|
||||
return nil, fmt.Errorf("unknown option '%s' for --enable-feature", feature)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -138,7 +138,7 @@ func FallbackMatcherParser(l *slog.Logger) ParseMatcher {
|
||||
}
|
||||
// If the input is valid in both parsers, but produces different results,
|
||||
// then there is disagreement.
|
||||
if nErr == nil && cErr == nil && !reflect.DeepEqual(nMatcher, cMatcher) {
|
||||
if cErr == nil && !reflect.DeepEqual(nMatcher, cMatcher) {
|
||||
l.Warn("Matchers input has disagreement", "input", input, "origin", origin)
|
||||
return cMatcher, nil
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func FallbackMatchersParser(l *slog.Logger) ParseMatchers {
|
||||
// If the input is valid in both parsers, but produces different results,
|
||||
// then there is disagreement. We need to compare to labels.Matchers(cMatchers)
|
||||
// as cMatchers is a []*labels.Matcher not labels.Matchers.
|
||||
if nErr == nil && cErr == nil && !reflect.DeepEqual(nMatchers, labels.Matchers(cMatchers)) {
|
||||
if cErr == nil && !reflect.DeepEqual(nMatchers, labels.Matchers(cMatchers)) {
|
||||
l.Warn("Matchers input has disagreement", "input", input, "origin", origin)
|
||||
return cMatchers, nil
|
||||
}
|
||||
@@ -190,7 +190,7 @@ func FallbackMatchersParser(l *slog.Logger) ParseMatchers {
|
||||
// isValidClassicLabelName returns true if the string is a valid classic label name.
|
||||
func isValidClassicLabelName(_ *slog.Logger) func(model.LabelName) bool {
|
||||
return func(name model.LabelName) bool {
|
||||
return name.IsValid()
|
||||
return model.LegacyValidation.IsValidLabelName(string(name))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -131,7 +131,7 @@ func (t *Template) FromGlob(path string) error {
|
||||
}
|
||||
|
||||
// ExecuteTextString needs a meaningful doc comment (TODO(fabxc)).
|
||||
func (t *Template) ExecuteTextString(text string, data interface{}) (string, error) {
|
||||
func (t *Template) ExecuteTextString(text string, data any) (string, error) {
|
||||
if text == "" {
|
||||
return "", nil
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func (t *Template) ExecuteTextString(text string, data interface{}) (string, err
|
||||
}
|
||||
|
||||
// ExecuteHTMLString needs a meaningful doc comment (TODO(fabxc)).
|
||||
func (t *Template) ExecuteHTMLString(html string, data interface{}) (string, error) {
|
||||
func (t *Template) ExecuteHTMLString(html string, data any) (string, error) {
|
||||
if html == "" {
|
||||
return "", nil
|
||||
}
|
||||
@@ -166,7 +166,7 @@ func (t *Template) ExecuteHTMLString(html string, data interface{}) (string, err
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
type FuncMap map[string]interface{}
|
||||
type FuncMap map[string]any
|
||||
|
||||
var DefaultFuncs = FuncMap{
|
||||
"toUpper": strings.ToUpper,
|
||||
|
||||
+11
-3
@@ -220,7 +220,7 @@ func extractSamples(f *dto.MetricFamily, o *DecodeOptions) (model.Vector, error)
|
||||
return extractSummary(o, f), nil
|
||||
case dto.MetricType_UNTYPED:
|
||||
return extractUntyped(o, f), nil
|
||||
case dto.MetricType_HISTOGRAM:
|
||||
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
|
||||
return extractHistogram(o, f), nil
|
||||
}
|
||||
return nil, fmt.Errorf("expfmt.extractSamples: unknown metric family type %v", f.GetType())
|
||||
@@ -403,9 +403,13 @@ func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
|
||||
infSeen = true
|
||||
}
|
||||
|
||||
v := q.GetCumulativeCountFloat()
|
||||
if v <= 0 {
|
||||
v = float64(q.GetCumulativeCount())
|
||||
}
|
||||
samples = append(samples, &model.Sample{
|
||||
Metric: model.Metric(lset),
|
||||
Value: model.SampleValue(q.GetCumulativeCount()),
|
||||
Value: model.SampleValue(v),
|
||||
Timestamp: timestamp,
|
||||
})
|
||||
}
|
||||
@@ -428,9 +432,13 @@ func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
|
||||
}
|
||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
|
||||
|
||||
v := m.Histogram.GetSampleCountFloat()
|
||||
if v <= 0 {
|
||||
v = float64(m.Histogram.GetSampleCount())
|
||||
}
|
||||
count := &model.Sample{
|
||||
Metric: model.Metric(lset),
|
||||
Value: model.SampleValue(m.Histogram.GetSampleCount()),
|
||||
Value: model.SampleValue(v),
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
samples = append(samples, count)
|
||||
|
||||
+18
-1
@@ -208,6 +208,8 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
|
||||
n, err = w.WriteString(" unknown\n")
|
||||
case dto.MetricType_HISTOGRAM:
|
||||
n, err = w.WriteString(" histogram\n")
|
||||
case dto.MetricType_GAUGE_HISTOGRAM:
|
||||
n, err = w.WriteString(" gaugehistogram\n")
|
||||
default:
|
||||
return written, fmt.Errorf("unknown metric type %s", metricType.String())
|
||||
}
|
||||
@@ -325,7 +327,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
|
||||
createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Summary.GetCreatedTimestamp())
|
||||
n += createdTsBytesWritten
|
||||
}
|
||||
case dto.MetricType_HISTOGRAM:
|
||||
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
|
||||
if metric.Histogram == nil {
|
||||
return written, fmt.Errorf(
|
||||
"expected histogram in metric %s %s", compliantName, metric,
|
||||
@@ -333,6 +335,12 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
|
||||
}
|
||||
infSeen := false
|
||||
for _, b := range metric.Histogram.Bucket {
|
||||
if b.GetCumulativeCountFloat() > 0 {
|
||||
return written, fmt.Errorf(
|
||||
"OpenMetrics v1.0 does not support float histogram %s %s",
|
||||
compliantName, metric,
|
||||
)
|
||||
}
|
||||
n, err = writeOpenMetricsSample(
|
||||
w, compliantName, "_bucket", metric,
|
||||
model.BucketLabel, b.GetUpperBound(),
|
||||
@@ -354,6 +362,9 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
|
||||
0, metric.Histogram.GetSampleCount(), true,
|
||||
nil,
|
||||
)
|
||||
// We do not check for a float sample count here
|
||||
// because we will check for it below (and error
|
||||
// out if needed).
|
||||
written += n
|
||||
if err != nil {
|
||||
return
|
||||
@@ -368,6 +379,12 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if metric.Histogram.GetSampleCountFloat() > 0 {
|
||||
return written, fmt.Errorf(
|
||||
"OpenMetrics v1.0 does not support float histogram %s %s",
|
||||
compliantName, metric,
|
||||
)
|
||||
}
|
||||
n, err = writeOpenMetricsSample(
|
||||
w, compliantName, "_count", metric, "", 0,
|
||||
0, metric.Histogram.GetSampleCount(), true,
|
||||
|
||||
+20
-8
@@ -151,7 +151,10 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
|
||||
n, err = w.WriteString(" summary\n")
|
||||
case dto.MetricType_UNTYPED:
|
||||
n, err = w.WriteString(" untyped\n")
|
||||
case dto.MetricType_HISTOGRAM:
|
||||
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
|
||||
// The classic Prometheus text format has no notion of a gauge
|
||||
// histogram. We render a gauge histogram in the same way as a
|
||||
// regular histogram.
|
||||
n, err = w.WriteString(" histogram\n")
|
||||
default:
|
||||
return written, fmt.Errorf("unknown metric type %s", metricType.String())
|
||||
@@ -223,7 +226,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
|
||||
w, name, "_count", metric, "", 0,
|
||||
float64(metric.Summary.GetSampleCount()),
|
||||
)
|
||||
case dto.MetricType_HISTOGRAM:
|
||||
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
|
||||
if metric.Histogram == nil {
|
||||
return written, fmt.Errorf(
|
||||
"expected histogram in metric %s %s", name, metric,
|
||||
@@ -231,10 +234,14 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
|
||||
}
|
||||
infSeen := false
|
||||
for _, b := range metric.Histogram.Bucket {
|
||||
v := b.GetCumulativeCountFloat()
|
||||
if v == 0 {
|
||||
v = float64(b.GetCumulativeCount())
|
||||
}
|
||||
n, err = writeSample(
|
||||
w, name, "_bucket", metric,
|
||||
model.BucketLabel, b.GetUpperBound(),
|
||||
float64(b.GetCumulativeCount()),
|
||||
v,
|
||||
)
|
||||
written += n
|
||||
if err != nil {
|
||||
@@ -245,10 +252,14 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
|
||||
}
|
||||
}
|
||||
if !infSeen {
|
||||
v := metric.Histogram.GetSampleCountFloat()
|
||||
if v == 0 {
|
||||
v = float64(metric.Histogram.GetSampleCount())
|
||||
}
|
||||
n, err = writeSample(
|
||||
w, name, "_bucket", metric,
|
||||
model.BucketLabel, math.Inf(+1),
|
||||
float64(metric.Histogram.GetSampleCount()),
|
||||
v,
|
||||
)
|
||||
written += n
|
||||
if err != nil {
|
||||
@@ -263,10 +274,11 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n, err = writeSample(
|
||||
w, name, "_count", metric, "", 0,
|
||||
float64(metric.Histogram.GetSampleCount()),
|
||||
)
|
||||
v := metric.Histogram.GetSampleCountFloat()
|
||||
if v == 0 {
|
||||
v = float64(metric.Histogram.GetSampleCount())
|
||||
}
|
||||
n, err = writeSample(w, name, "_count", metric, "", 0, v)
|
||||
default:
|
||||
return written, fmt.Errorf(
|
||||
"unexpected type in metric %s %s", name, metric,
|
||||
|
||||
+83
-19
@@ -48,8 +48,10 @@ func (e ParseError) Error() string {
|
||||
return fmt.Sprintf("text format parsing error in line %d: %s", e.Line, e.Msg)
|
||||
}
|
||||
|
||||
// TextParser is used to parse the simple and flat text-based exchange format. Its
|
||||
// zero value is ready to use.
|
||||
// TextParser is used to parse the simple and flat text-based exchange format.
|
||||
//
|
||||
// TextParser instances must be created with NewTextParser, the zero value of
|
||||
// TextParser is invalid.
|
||||
type TextParser struct {
|
||||
metricFamiliesByName map[string]*dto.MetricFamily
|
||||
buf *bufio.Reader // Where the parsed input is read through.
|
||||
@@ -129,9 +131,44 @@ func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricF
|
||||
if p.err != nil && errors.Is(p.err, io.EOF) {
|
||||
p.parseError("unexpected end of input stream")
|
||||
}
|
||||
for _, histogramMetric := range p.histograms {
|
||||
normalizeHistogram(histogramMetric.GetHistogram())
|
||||
}
|
||||
return p.metricFamiliesByName, p.err
|
||||
}
|
||||
|
||||
// normalizeHistogram makes sure that all the buckets and the count in each
|
||||
// histogram is either completely float or completely integer.
|
||||
func normalizeHistogram(histogram *dto.Histogram) {
|
||||
if histogram == nil {
|
||||
return
|
||||
}
|
||||
anyFloats := false
|
||||
if histogram.GetSampleCountFloat() != 0 {
|
||||
anyFloats = true
|
||||
} else {
|
||||
for _, b := range histogram.GetBucket() {
|
||||
if b.GetCumulativeCountFloat() != 0 {
|
||||
anyFloats = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !anyFloats {
|
||||
return
|
||||
}
|
||||
if histogram.GetSampleCountFloat() == 0 {
|
||||
histogram.SampleCountFloat = proto.Float64(float64(histogram.GetSampleCount()))
|
||||
histogram.SampleCount = nil
|
||||
}
|
||||
for _, b := range histogram.GetBucket() {
|
||||
if b.GetCumulativeCountFloat() == 0 {
|
||||
b.CumulativeCountFloat = proto.Float64(float64(b.GetCumulativeCount()))
|
||||
b.CumulativeCount = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *TextParser) reset(in io.Reader) {
|
||||
p.metricFamiliesByName = map[string]*dto.MetricFamily{}
|
||||
p.currentLabelPairs = nil
|
||||
@@ -281,7 +318,9 @@ func (p *TextParser) readingLabels() stateFn {
|
||||
// Summaries/histograms are special. We have to reset the
|
||||
// currentLabels map, currentQuantile and currentBucket before starting to
|
||||
// read labels.
|
||||
if p.currentMF.GetType() == dto.MetricType_SUMMARY || p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
||||
if p.currentMF.GetType() == dto.MetricType_SUMMARY ||
|
||||
p.currentMF.GetType() == dto.MetricType_HISTOGRAM ||
|
||||
p.currentMF.GetType() == dto.MetricType_GAUGE_HISTOGRAM {
|
||||
p.currentLabels = map[string]string{}
|
||||
p.currentLabels[string(model.MetricNameLabel)] = p.currentMF.GetName()
|
||||
p.currentQuantile = math.NaN()
|
||||
@@ -374,7 +413,9 @@ func (p *TextParser) startLabelName() stateFn {
|
||||
// Special summary/histogram treatment. Don't add 'quantile' and 'le'
|
||||
// labels to 'real' labels.
|
||||
if (p.currentMF.GetType() != dto.MetricType_SUMMARY || p.currentLabelPair.GetName() != model.QuantileLabel) &&
|
||||
(p.currentMF.GetType() != dto.MetricType_HISTOGRAM || p.currentLabelPair.GetName() != model.BucketLabel) {
|
||||
((p.currentMF.GetType() != dto.MetricType_HISTOGRAM &&
|
||||
p.currentMF.GetType() != dto.MetricType_GAUGE_HISTOGRAM) ||
|
||||
p.currentLabelPair.GetName() != model.BucketLabel) {
|
||||
p.currentLabelPairs = append(p.currentLabelPairs, p.currentLabelPair)
|
||||
}
|
||||
// Check for duplicate label names.
|
||||
@@ -425,7 +466,7 @@ func (p *TextParser) startLabelValue() stateFn {
|
||||
}
|
||||
}
|
||||
// Similar special treatment of histograms.
|
||||
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
||||
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM || p.currentMF.GetType() == dto.MetricType_GAUGE_HISTOGRAM {
|
||||
if p.currentLabelPair.GetName() == model.BucketLabel {
|
||||
if p.currentBucket, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil {
|
||||
// Create a more helpful error message.
|
||||
@@ -476,7 +517,7 @@ func (p *TextParser) readingValue() stateFn {
|
||||
p.summaries[signature] = p.currentMetric
|
||||
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
|
||||
}
|
||||
case dto.MetricType_HISTOGRAM:
|
||||
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
|
||||
signature := model.LabelsToSignature(p.currentLabels)
|
||||
if histogram := p.histograms[signature]; histogram != nil {
|
||||
p.currentMetric = histogram
|
||||
@@ -522,24 +563,38 @@ func (p *TextParser) readingValue() stateFn {
|
||||
},
|
||||
)
|
||||
}
|
||||
case dto.MetricType_HISTOGRAM:
|
||||
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
|
||||
// *sigh*
|
||||
if p.currentMetric.Histogram == nil {
|
||||
p.currentMetric.Histogram = &dto.Histogram{}
|
||||
}
|
||||
switch {
|
||||
case p.currentIsHistogramCount:
|
||||
p.currentMetric.Histogram.SampleCount = proto.Uint64(uint64(value))
|
||||
if uintValue := uint64(value); value == float64(uintValue) {
|
||||
p.currentMetric.Histogram.SampleCount = proto.Uint64(uintValue)
|
||||
} else {
|
||||
if value < 0 {
|
||||
p.parseError(fmt.Sprintf("negative count for histogram %q", p.currentMF.GetName()))
|
||||
return nil
|
||||
}
|
||||
p.currentMetric.Histogram.SampleCountFloat = proto.Float64(value)
|
||||
}
|
||||
case p.currentIsHistogramSum:
|
||||
p.currentMetric.Histogram.SampleSum = proto.Float64(value)
|
||||
case !math.IsNaN(p.currentBucket):
|
||||
p.currentMetric.Histogram.Bucket = append(
|
||||
p.currentMetric.Histogram.Bucket,
|
||||
&dto.Bucket{
|
||||
UpperBound: proto.Float64(p.currentBucket),
|
||||
CumulativeCount: proto.Uint64(uint64(value)),
|
||||
},
|
||||
)
|
||||
b := &dto.Bucket{
|
||||
UpperBound: proto.Float64(p.currentBucket),
|
||||
}
|
||||
if uintValue := uint64(value); value == float64(uintValue) {
|
||||
b.CumulativeCount = proto.Uint64(uintValue)
|
||||
} else {
|
||||
if value < 0 {
|
||||
p.parseError(fmt.Sprintf("negative bucket population for histogram %q", p.currentMF.GetName()))
|
||||
return nil
|
||||
}
|
||||
b.CumulativeCountFloat = proto.Float64(value)
|
||||
}
|
||||
p.currentMetric.Histogram.Bucket = append(p.currentMetric.Histogram.Bucket, b)
|
||||
}
|
||||
default:
|
||||
p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName())
|
||||
@@ -602,10 +657,18 @@ func (p *TextParser) readingType() stateFn {
|
||||
if p.readTokenUntilNewline(false); p.err != nil {
|
||||
return nil // Unexpected end of input.
|
||||
}
|
||||
metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())]
|
||||
typ := strings.ToUpper(p.currentToken.String()) // Tolerate any combination of upper and lower case.
|
||||
metricType, ok := dto.MetricType_value[typ] // Tolerate "gauge_histogram" (not originally part of the text format).
|
||||
if !ok {
|
||||
p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String()))
|
||||
return nil
|
||||
// We also want to tolerate "gaugehistogram" to mark a gauge
|
||||
// histogram, because that string is used in OpenMetrics. Note,
|
||||
// however, that gauge histograms do not officially exist in the
|
||||
// classic text format.
|
||||
if typ != "GAUGEHISTOGRAM" {
|
||||
p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String()))
|
||||
return nil
|
||||
}
|
||||
metricType = int32(dto.MetricType_GAUGE_HISTOGRAM)
|
||||
}
|
||||
p.currentMF.Type = dto.MetricType(metricType).Enum()
|
||||
return p.startOfLine
|
||||
@@ -855,7 +918,8 @@ func (p *TextParser) setOrCreateCurrentMF() {
|
||||
}
|
||||
histogramName := histogramMetricName(name)
|
||||
if p.currentMF = p.metricFamiliesByName[histogramName]; p.currentMF != nil {
|
||||
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
||||
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM ||
|
||||
p.currentMF.GetType() == dto.MetricType_GAUGE_HISTOGRAM {
|
||||
if isCount(name) {
|
||||
p.currentIsHistogramCount = true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user