reva-bump-2.39.2. update opencloud 4.0.0-rc.1

This commit is contained in:
Viktor Scharf
2025-11-12 20:26:41 +01:00
parent cd3101ca31
commit 68ad1e52c4
44 changed files with 675 additions and 10579 deletions
+1 -1
View File
@@ -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
View File
@@ -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
}
@@ -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 ""
@@ -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 {
@@ -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
}
@@ -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
}
@@ -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.
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}