mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
Merge cdf4b3ec3a
into 61aa82865d
This commit is contained in:
commit
19345549f0
26
cmd/prometheus/labels.go
Normal file
26
cmd/prometheus/labels.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2017 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.
|
||||
|
||||
//go:build !stringlabels
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/prometheus/prometheus/tsdb"
|
||||
)
|
||||
|
||||
func mapCommonLabelSymbols(_ *tsdb.DB, _ *slog.Logger) error {
|
||||
return nil
|
||||
}
|
136
cmd/prometheus/labels_stringlabels.go
Normal file
136
cmd/prometheus/labels_stringlabels.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
// Copyright 2017 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.
|
||||
|
||||
//go:build stringlabels
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/prometheus/model/labels"
|
||||
"github.com/prometheus/prometheus/tsdb"
|
||||
"github.com/prometheus/prometheus/tsdb/index"
|
||||
)
|
||||
|
||||
// countBlockSymbols reads given block index and counts how many time each string
|
||||
// occurs on time series labels.
|
||||
func countBlockSymbols(ctx context.Context, block *tsdb.Block) (map[string]int, error) {
|
||||
names := map[string]int{}
|
||||
|
||||
ir, err := block.Index()
|
||||
if err != nil {
|
||||
return names, err
|
||||
}
|
||||
|
||||
labelNames, err := ir.LabelNames(ctx)
|
||||
if err != nil {
|
||||
return names, err
|
||||
}
|
||||
|
||||
for _, name := range labelNames {
|
||||
name = strings.Clone(name)
|
||||
|
||||
if _, ok := names[name]; !ok {
|
||||
names[name] = 0
|
||||
}
|
||||
|
||||
values, err := ir.LabelValues(ctx, name)
|
||||
if err != nil {
|
||||
return names, err
|
||||
}
|
||||
for _, value := range values {
|
||||
value = strings.Clone(value)
|
||||
|
||||
if _, ok := names[value]; !ok {
|
||||
names[value] = 0
|
||||
}
|
||||
|
||||
p, err := ir.Postings(ctx, name, value)
|
||||
if err != nil {
|
||||
return names, err
|
||||
}
|
||||
|
||||
refs, err := index.ExpandPostings(p)
|
||||
if err != nil {
|
||||
return names, err
|
||||
}
|
||||
|
||||
names[name] += len(refs)
|
||||
names[value] += len(refs)
|
||||
}
|
||||
}
|
||||
return names, ir.Close()
|
||||
}
|
||||
|
||||
type labelCost struct {
|
||||
name string
|
||||
cost int
|
||||
}
|
||||
|
||||
// selectBlockStringsToMap takes a block and returns a list of strings that are most commonly
|
||||
// present on all time series.
|
||||
// List is sorted starting with the most frequent strings.
|
||||
func selectBlockStringsToMap(block *tsdb.Block) ([]string, error) {
|
||||
names, err := countBlockSymbols(context.Background(), block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build list of common strings in block %s: %w", block.Meta().ULID, err)
|
||||
}
|
||||
|
||||
costs := make([]labelCost, 0, len(names))
|
||||
for name, count := range names {
|
||||
costs = append(costs, labelCost{name: name, cost: (len(name) - 1) * count})
|
||||
}
|
||||
slices.SortFunc(costs, func(a, b labelCost) int {
|
||||
return cmp.Compare(b.cost, a.cost)
|
||||
})
|
||||
|
||||
mappedLabels := make([]string, 0, 256)
|
||||
for i, c := range costs {
|
||||
if i >= 256 {
|
||||
break
|
||||
}
|
||||
mappedLabels = append(mappedLabels, c.name)
|
||||
}
|
||||
return mappedLabels, nil
|
||||
}
|
||||
|
||||
func mapCommonLabelSymbols(db *tsdb.DB, logger *slog.Logger) error {
|
||||
var block *tsdb.Block
|
||||
for _, b := range db.Blocks() {
|
||||
if block == nil || b.MaxTime() > block.MaxTime() {
|
||||
block = b
|
||||
}
|
||||
}
|
||||
if block == nil {
|
||||
logger.Info("No tsdb blocks found, can't map common label strings")
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Info(
|
||||
"Finding most common label strings in last block",
|
||||
slog.String("block", block.String()),
|
||||
)
|
||||
mappedLabels, err := selectBlockStringsToMap(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("Mapped common label strings", slog.Int("count", len(mappedLabels)))
|
||||
labels.MapLabels(mappedLabels)
|
||||
return nil
|
||||
}
|
|
@ -853,6 +853,12 @@ func main() {
|
|||
|
||||
cfg.web.Flags = map[string]string{}
|
||||
|
||||
cfg.tsdb.PreInitFunc = func(db *tsdb.DB) {
|
||||
if err = mapCommonLabelSymbols(db, logger); err != nil {
|
||||
logger.Warn("Failed to map common strings in labels", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude kingpin default flags to expose only Prometheus ones.
|
||||
boilerplateFlags := kingpin.New("", "").Version("")
|
||||
for _, f := range a.Model().Flags {
|
||||
|
@ -1797,6 +1803,7 @@ type tsdbOptions struct {
|
|||
CompactionDelayMaxPercent int
|
||||
EnableOverlappingCompaction bool
|
||||
EnableOOONativeHistograms bool
|
||||
PreInitFunc tsdb.PreInitFunc
|
||||
}
|
||||
|
||||
func (opts tsdbOptions) ToTSDBOptions() tsdb.Options {
|
||||
|
@ -1821,6 +1828,7 @@ func (opts tsdbOptions) ToTSDBOptions() tsdb.Options {
|
|||
EnableDelayedCompaction: opts.EnableDelayedCompaction,
|
||||
CompactionDelayMaxPercent: opts.CompactionDelayMaxPercent,
|
||||
EnableOverlappingCompaction: opts.EnableOverlappingCompaction,
|
||||
PreInitFunc: opts.PreInitFunc,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,124 @@ import (
|
|||
"github.com/cespare/xxhash/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// List of labels that should be mapped to a single byte value.
|
||||
// Obviously can't have more than 256 here.
|
||||
mappedLabels = []string{}
|
||||
mappedLabelIndex = map[string]byte{}
|
||||
)
|
||||
|
||||
// MapLabels takes a list of strings that shuld use a single byte storage
|
||||
// inside labels, making them use as little memory as possible.
|
||||
// Since we use a single byte mapping we can only have 256 such strings.
|
||||
//
|
||||
// We MUST store empty string ("") as one of the values here and if you
|
||||
// don't pass it into MapLabels() then it will be injected.
|
||||
//
|
||||
// If you pass more strings than 256 then extra strings will be ignored.
|
||||
func MapLabels(names []string) {
|
||||
// We must always store empty string. Push it to the front of the slice if not present.
|
||||
if !slices.Contains(names, "") {
|
||||
names = append([]string{""}, names...)
|
||||
}
|
||||
|
||||
mappedLabels = make([]string, 0, 256)
|
||||
mappedLabelIndex = make(map[string]byte, 256)
|
||||
|
||||
for i, name := range names {
|
||||
if i >= 256 {
|
||||
break
|
||||
}
|
||||
mappedLabels = append(mappedLabels, name)
|
||||
mappedLabelIndex[name] = byte(i)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
names := []string{
|
||||
// Empty string, this must be present here.
|
||||
"",
|
||||
// These label names are always present on every time series.
|
||||
MetricName,
|
||||
InstanceName,
|
||||
"job",
|
||||
// Common label names.
|
||||
BucketLabel,
|
||||
"code",
|
||||
"handler",
|
||||
"quantile",
|
||||
// Meta metric names injected by Prometheus itself.
|
||||
"scrape_body_size_bytes",
|
||||
"scrape_duration_seconds",
|
||||
"scrape_sample_limit",
|
||||
"scrape_samples_post_metric_relabeling",
|
||||
"scrape_samples_scraped",
|
||||
"scrape_series_added",
|
||||
"scrape_timeout_seconds",
|
||||
// Common metric names from client libraries.
|
||||
"process_cpu_seconds_total",
|
||||
"process_max_fds",
|
||||
"process_network_receive_bytes_total",
|
||||
"process_network_transmit_bytes_total",
|
||||
"process_open_fds",
|
||||
"process_resident_memory_bytes",
|
||||
"process_start_time_seconds ",
|
||||
"process_virtual_memory_bytes",
|
||||
"process_virtual_memory_max_bytes",
|
||||
// client_go specific metrics
|
||||
"go_gc_heap_frees_by_size_bytes_bucket",
|
||||
"go_gc_heap_allocs_by_size_bytes_bucket",
|
||||
"net_conntrack_dialer_conn_failed_total",
|
||||
"go_sched_pauses_total_other_seconds_bucket",
|
||||
"go_sched_pauses_total_gc_seconds_bucket",
|
||||
"go_sched_pauses_stopping_other_seconds_bucket",
|
||||
"go_sched_pauses_stopping_gc_seconds_bucket",
|
||||
"go_sched_latencies_seconds_bucket",
|
||||
"go_gc_pauses_seconds_bucket",
|
||||
"go_gc_duration_seconds",
|
||||
// node_exporter metrics
|
||||
"node_cpu_seconds_total",
|
||||
"node_scrape_collector_success",
|
||||
"node_scrape_collector_duration_seconds",
|
||||
"node_cpu_scaling_governor",
|
||||
"node_cpu_guest_seconds_total",
|
||||
"node_hwmon_temp_celsius",
|
||||
"node_hwmon_sensor_label",
|
||||
"node_hwmon_temp_max_celsius",
|
||||
"node_cooling_device_max_state",
|
||||
"node_cooling_device_cur_state",
|
||||
"node_softnet_times_squeezed_total",
|
||||
"node_softnet_received_rps_total",
|
||||
"node_softnet_processed_total",
|
||||
"node_softnet_flow_limit_count_total",
|
||||
"node_softnet_dropped_total",
|
||||
"node_softnet_cpu_collision_total",
|
||||
"node_softnet_backlog_len",
|
||||
"node_schedstat_waiting_seconds_total",
|
||||
"node_schedstat_timeslices_total",
|
||||
"node_schedstat_running_seconds_total",
|
||||
"node_cpu_scaling_frequency_min_hertz",
|
||||
"node_cpu_scaling_frequency_max_hertz",
|
||||
"node_cpu_scaling_frequency_hertz",
|
||||
"node_cpu_frequency_min_hertz",
|
||||
"node_cpu_frequency_max_hertz",
|
||||
"node_hwmon_temp_crit_celsius",
|
||||
"node_hwmon_temp_crit_alarm_celsius",
|
||||
"node_cpu_core_throttles_total",
|
||||
"node_thermal_zone_temp",
|
||||
"node_hwmon_temp_min_celsius",
|
||||
"node_hwmon_chip_names",
|
||||
"node_filesystem_readonly",
|
||||
"node_filesystem_device_error",
|
||||
"node_filesystem_size_bytes",
|
||||
"node_filesystem_free_bytes",
|
||||
"node_filesystem_files_free",
|
||||
"node_filesystem_files",
|
||||
"node_filesystem_avail_bytes",
|
||||
}
|
||||
MapLabels(names)
|
||||
}
|
||||
|
||||
// Labels is implemented by a single flat string holding name/value pairs.
|
||||
// Each name and value is preceded by its length in varint encoding.
|
||||
// Names are in order.
|
||||
|
@ -30,12 +148,15 @@ type Labels struct {
|
|||
data string
|
||||
}
|
||||
|
||||
func decodeSize(data string, index int) (int, int) {
|
||||
func decodeSize(data string, index int) (int, int, bool) {
|
||||
// Fast-path for common case of a single byte, value 0..127.
|
||||
b := data[index]
|
||||
index++
|
||||
if b == 0x0 {
|
||||
return 1, index, true
|
||||
}
|
||||
if b < 0x80 {
|
||||
return int(b), index
|
||||
return int(b), index, false
|
||||
}
|
||||
size := int(b & 0x7F)
|
||||
for shift := uint(7); ; shift += 7 {
|
||||
|
@ -48,15 +169,27 @@ func decodeSize(data string, index int) (int, int) {
|
|||
break
|
||||
}
|
||||
}
|
||||
return size, index
|
||||
return size, index, false
|
||||
}
|
||||
|
||||
func decodeString(data string, index int) (string, int) {
|
||||
var size int
|
||||
size, index = decodeSize(data, index)
|
||||
var mapped bool
|
||||
size, index, mapped = decodeSize(data, index)
|
||||
if mapped {
|
||||
b := data[index]
|
||||
return mappedLabels[int(b)], index + size
|
||||
}
|
||||
return data[index : index+size], index + size
|
||||
}
|
||||
|
||||
func encodeShortString(s string) (int, byte) {
|
||||
if i, ok := mappedLabelIndex[s]; ok {
|
||||
return 0, i
|
||||
}
|
||||
return len(s), 0
|
||||
}
|
||||
|
||||
// Bytes returns ls as a byte slice.
|
||||
// It uses non-printing characters and so should not be used for printing.
|
||||
func (ls Labels) Bytes(buf []byte) []byte {
|
||||
|
@ -197,11 +330,24 @@ func (ls Labels) Get(name string) string {
|
|||
return "" // Prometheus does not store blank label names.
|
||||
}
|
||||
for i := 0; i < len(ls.data); {
|
||||
var size int
|
||||
size, i = decodeSize(ls.data, i)
|
||||
var size, next int
|
||||
var mapped bool
|
||||
var lName, lValue string
|
||||
size, next, mapped = decodeSize(ls.data, i) // Read the key index and size.
|
||||
if mapped { // Key is a mapped string, so decode it fully and move i to the value index.
|
||||
lName, i = decodeString(ls.data, i)
|
||||
if lName == name {
|
||||
lValue, _ = decodeString(ls.data, i)
|
||||
return lValue
|
||||
}
|
||||
if lName[0] > name[0] { // Stop looking if we've gone past.
|
||||
break
|
||||
}
|
||||
} else { // Value is stored raw in the data string.
|
||||
i = next // Move index to the start of the key string.
|
||||
if ls.data[i] == name[0] {
|
||||
lName := ls.data[i : i+size]
|
||||
i += size
|
||||
lName = ls.data[i : i+size]
|
||||
i += size // We got the key string, move the index to the start of the value.
|
||||
if lName == name {
|
||||
lValue, _ := decodeString(ls.data, i)
|
||||
return lValue
|
||||
|
@ -212,8 +358,9 @@ func (ls Labels) Get(name string) string {
|
|||
}
|
||||
i += size
|
||||
}
|
||||
size, i = decodeSize(ls.data, i)
|
||||
i += size
|
||||
}
|
||||
size, i, _ = decodeSize(ls.data, i) // Read the value index and size.
|
||||
i += size // move the index past the value so we can read the next key.
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -224,11 +371,22 @@ func (ls Labels) Has(name string) bool {
|
|||
return false // Prometheus does not store blank label names.
|
||||
}
|
||||
for i := 0; i < len(ls.data); {
|
||||
var size int
|
||||
size, i = decodeSize(ls.data, i)
|
||||
var size, next int
|
||||
var mapped bool
|
||||
var lName string
|
||||
size, next, mapped = decodeSize(ls.data, i)
|
||||
if mapped {
|
||||
lName, i = decodeString(ls.data, i)
|
||||
if lName == name {
|
||||
return true
|
||||
}
|
||||
if lName[0] > name[0] { // Stop looking if we've gone past.
|
||||
break
|
||||
}
|
||||
} else {
|
||||
i = next
|
||||
if ls.data[i] == name[0] {
|
||||
lName := ls.data[i : i+size]
|
||||
i += size
|
||||
lName = ls.data[i : i+size]
|
||||
if lName == name {
|
||||
return true
|
||||
}
|
||||
|
@ -236,9 +394,10 @@ func (ls Labels) Has(name string) bool {
|
|||
if ls.data[i] > name[0] { // Stop looking if we've gone past.
|
||||
break
|
||||
}
|
||||
}
|
||||
i += size
|
||||
}
|
||||
size, i = decodeSize(ls.data, i)
|
||||
size, i, _ = decodeSize(ls.data, i)
|
||||
i += size
|
||||
}
|
||||
return false
|
||||
|
@ -356,10 +515,10 @@ func Compare(a, b Labels) int {
|
|||
// Now we know that there is some difference before the end of a and b.
|
||||
// Go back through the fields and find which field that difference is in.
|
||||
firstCharDifferent, i := i, 0
|
||||
size, nextI := decodeSize(a.data, i)
|
||||
size, nextI, _ := decodeSize(a.data, i)
|
||||
for nextI+size <= firstCharDifferent {
|
||||
i = nextI + size
|
||||
size, nextI = decodeSize(a.data, i)
|
||||
size, nextI, _ = decodeSize(a.data, i)
|
||||
}
|
||||
// Difference is inside this entry.
|
||||
aStr, _ := decodeString(a.data, i)
|
||||
|
@ -385,9 +544,9 @@ func (ls Labels) Len() int {
|
|||
count := 0
|
||||
for i := 0; i < len(ls.data); {
|
||||
var size int
|
||||
size, i = decodeSize(ls.data, i)
|
||||
size, i, _ = decodeSize(ls.data, i)
|
||||
i += size
|
||||
size, i = decodeSize(ls.data, i)
|
||||
size, i, _ = decodeSize(ls.data, i)
|
||||
i += size
|
||||
count++
|
||||
}
|
||||
|
@ -422,7 +581,7 @@ func (ls Labels) Validate(f func(l Label) error) error {
|
|||
func (ls Labels) DropMetricName() Labels {
|
||||
for i := 0; i < len(ls.data); {
|
||||
lName, i2 := decodeString(ls.data, i)
|
||||
size, i2 := decodeSize(ls.data, i2)
|
||||
size, i2, _ := decodeSize(ls.data, i2)
|
||||
i2 += size
|
||||
if lName == MetricName {
|
||||
if i == 0 { // Make common case fast with no allocations.
|
||||
|
@ -518,12 +677,27 @@ func marshalLabelsToSizedBuffer(lbls []Label, data []byte) int {
|
|||
|
||||
func marshalLabelToSizedBuffer(m *Label, data []byte) int {
|
||||
i := len(data)
|
||||
i -= len(m.Value)
|
||||
|
||||
size, b := encodeShortString(m.Value)
|
||||
if size == 0 {
|
||||
i--
|
||||
data[i] = b
|
||||
} else {
|
||||
i -= size
|
||||
copy(data[i:], m.Value)
|
||||
i = encodeSize(data, i, len(m.Value))
|
||||
i -= len(m.Name)
|
||||
}
|
||||
i = encodeSize(data, i, size)
|
||||
|
||||
size, b = encodeShortString(m.Name)
|
||||
if size == 0 {
|
||||
i--
|
||||
data[i] = b
|
||||
} else {
|
||||
i -= size
|
||||
copy(data[i:], m.Name)
|
||||
i = encodeSize(data, i, len(m.Name))
|
||||
}
|
||||
i = encodeSize(data, i, size)
|
||||
|
||||
return len(data) - i
|
||||
}
|
||||
|
||||
|
@ -581,9 +755,16 @@ func labelsSize(lbls []Label) (n int) {
|
|||
|
||||
func labelSize(m *Label) (n int) {
|
||||
// strings are encoded as length followed by contents.
|
||||
l := len(m.Name)
|
||||
l, _ := encodeShortString(m.Name)
|
||||
if l == 0 {
|
||||
l++
|
||||
}
|
||||
n += l + sizeVarint(uint64(l))
|
||||
l = len(m.Value)
|
||||
|
||||
l, _ = encodeShortString(m.Value)
|
||||
if l == 0 {
|
||||
l++
|
||||
}
|
||||
n += l + sizeVarint(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
|
|
@ -82,8 +82,8 @@ func labelsWithHashCollision() (labels.Labels, labels.Labels) {
|
|||
|
||||
if ls1.Hash() != ls2.Hash() {
|
||||
// These ones are the same when using -tags stringlabels
|
||||
ls1 = labels.FromStrings("__name__", "metric", "lbl", "HFnEaGl")
|
||||
ls2 = labels.FromStrings("__name__", "metric", "lbl", "RqcXatm")
|
||||
ls1 = labels.FromStrings("__name__", "metric", "lbl", "D3opXYk")
|
||||
ls2 = labels.FromStrings("__name__", "metric", "lbl", "G1__3.m")
|
||||
}
|
||||
|
||||
if ls1.Hash() != ls2.Hash() {
|
||||
|
|
|
@ -224,6 +224,9 @@ type Options struct {
|
|||
// PostingsDecoderFactory allows users to customize postings decoders based on BlockMeta.
|
||||
// By default, DefaultPostingsDecoderFactory will be used to create raw posting decoder.
|
||||
PostingsDecoderFactory PostingsDecoderFactory
|
||||
|
||||
// PreInitFunc is a function that will be called before the HEAD is initialized.
|
||||
PreInitFunc PreInitFunc
|
||||
}
|
||||
|
||||
type NewCompactorFunc func(ctx context.Context, r prometheus.Registerer, l *slog.Logger, ranges []int64, pool chunkenc.Pool, opts *Options) (Compactor, error)
|
||||
|
@ -234,6 +237,8 @@ type BlockQuerierFunc func(b BlockReader, mint, maxt int64) (storage.Querier, er
|
|||
|
||||
type BlockChunkQuerierFunc func(b BlockReader, mint, maxt int64) (storage.ChunkQuerier, error)
|
||||
|
||||
type PreInitFunc func(*DB)
|
||||
|
||||
// DB handles reads and writes of time series falling into
|
||||
// a hashed partition of a seriedb.
|
||||
type DB struct {
|
||||
|
@ -1011,6 +1016,10 @@ func open(dir string, l *slog.Logger, r prometheus.Registerer, opts *Options, rn
|
|||
minValidTime = inOrderMaxTime
|
||||
}
|
||||
|
||||
if db.opts.PreInitFunc != nil {
|
||||
db.opts.PreInitFunc(db)
|
||||
}
|
||||
|
||||
if initErr := db.head.Init(minValidTime); initErr != nil {
|
||||
db.head.metrics.walCorruptionsTotal.Inc()
|
||||
var e *errLoadWbl
|
||||
|
|
|
@ -6247,8 +6247,8 @@ func labelsWithHashCollision() (labels.Labels, labels.Labels) {
|
|||
|
||||
if ls1.Hash() != ls2.Hash() {
|
||||
// These ones are the same when using -tags stringlabels
|
||||
ls1 = labels.FromStrings("__name__", "metric", "lbl", "HFnEaGl")
|
||||
ls2 = labels.FromStrings("__name__", "metric", "lbl", "RqcXatm")
|
||||
ls1 = labels.FromStrings("__name__", "metric", "lbl", "D3opXYk")
|
||||
ls2 = labels.FromStrings("__name__", "metric", "lbl", "G1__3.m")
|
||||
}
|
||||
|
||||
if ls1.Hash() != ls2.Hash() {
|
||||
|
|
Loading…
Reference in a new issue