mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
339 lines
20 KiB
Go
339 lines
20 KiB
Go
package overrides
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/prometheus/common/config"
|
|
|
|
"github.com/grafana/tempo/modules/overrides/histograms"
|
|
"github.com/grafana/tempo/pkg/util/listtomap"
|
|
"github.com/grafana/tempo/tempodb/backend"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/grafana/tempo/pkg/sharedconfig"
|
|
filterconfig "github.com/grafana/tempo/pkg/spanfilter/config"
|
|
|
|
"github.com/prometheus/common/model"
|
|
)
|
|
|
|
type ConfigType string
|
|
|
|
const (
|
|
ConfigTypeLegacy ConfigType = "legacy"
|
|
ConfigTypeNew ConfigType = "new"
|
|
)
|
|
|
|
const (
|
|
// LocalIngestionRateStrategy indicates that this limit can be evaluated in local terms only
|
|
LocalIngestionRateStrategy = "local"
|
|
// GlobalIngestionRateStrategy indicates that an attempt should be made to consider this limit across the entire Tempo cluster
|
|
GlobalIngestionRateStrategy = "global"
|
|
|
|
// ErrorPrefixLiveTracesExceeded is used to flag batches from the ingester that were rejected b/c they had too many traces
|
|
ErrorPrefixLiveTracesExceeded = "LIVE_TRACES_EXCEEDED"
|
|
// ErrorPrefixTraceTooLarge is used to flag batches from the ingester that were rejected b/c they exceeded the single trace limit
|
|
ErrorPrefixTraceTooLarge = "TRACE_TOO_LARGE"
|
|
// ErrorPrefixRateLimited is used to flag batches that have exceeded the spans/second of the tenant
|
|
ErrorPrefixRateLimited = "RATE_LIMITED"
|
|
|
|
// metrics
|
|
MetricMaxLocalTracesPerUser = "max_local_traces_per_user"
|
|
MetricMaxGlobalTracesPerUser = "max_global_traces_per_user"
|
|
MetricMaxBytesPerTrace = "max_bytes_per_trace"
|
|
MetricMaxBytesPerTagValuesQuery = "max_bytes_per_tag_values_query"
|
|
MetricMaxBlocksPerTagValuesQuery = "max_blocks_per_tag_values_query"
|
|
MetricIngestionRateLimitBytes = "ingestion_rate_limit_bytes"
|
|
MetricIngestionBurstSizeBytes = "ingestion_burst_size_bytes"
|
|
MetricBlockRetention = "block_retention"
|
|
MetricCompactionWindow = "compaction_window"
|
|
MetricMetricsGeneratorMaxActiveSeries = "metrics_generator_max_active_series"
|
|
MetricsGeneratorDryRunEnabled = "metrics_generator_dry_run_enabled"
|
|
)
|
|
|
|
var metricLimitsDesc = prometheus.NewDesc(
|
|
"tempo_limits_defaults",
|
|
"Default resource limits",
|
|
[]string{"limit_name"},
|
|
nil,
|
|
)
|
|
|
|
type IngestionOverrides struct {
|
|
// Distributor enforced limits.
|
|
RateStrategy string `yaml:"rate_strategy,omitempty" json:"rate_strategy,omitempty"`
|
|
RateLimitBytes int `yaml:"rate_limit_bytes,omitempty" json:"rate_limit_bytes,omitempty"`
|
|
BurstSizeBytes int `yaml:"burst_size_bytes,omitempty" json:"burst_size_bytes,omitempty"`
|
|
|
|
// Ingester enforced limits.
|
|
MaxLocalTracesPerUser int `yaml:"max_traces_per_user,omitempty" json:"max_traces_per_user,omitempty"`
|
|
MaxGlobalTracesPerUser int `yaml:"max_global_traces_per_user,omitempty" json:"max_global_traces_per_user,omitempty"`
|
|
|
|
TenantShardSize int `yaml:"tenant_shard_size,omitempty" json:"tenant_shard_size,omitempty"`
|
|
MaxAttributeBytes int `yaml:"max_attribute_bytes,omitempty" json:"max_attribute_bytes,omitempty"`
|
|
ArtificialDelay *time.Duration `yaml:"artificial_delay,omitempty" json:"artificial_delay,omitempty"`
|
|
RetryInfoEnabled bool `yaml:"retry_info_enabled,omitempty" json:"retry_info_enabled,omitempty"`
|
|
}
|
|
|
|
type ForwarderOverrides struct {
|
|
QueueSize int `yaml:"queue_size,omitempty" json:"queue_size,omitempty"`
|
|
Workers int `yaml:"workers,omitempty" json:"workers,omitempty"`
|
|
}
|
|
|
|
type ServiceGraphsOverrides struct {
|
|
HistogramBuckets []float64 `yaml:"histogram_buckets,omitempty" json:"histogram_buckets,omitempty"`
|
|
Dimensions []string `yaml:"dimensions,omitempty" json:"dimensions,omitempty"`
|
|
PeerAttributes []string `yaml:"peer_attributes,omitempty" json:"peer_attributes,omitempty"`
|
|
FilterPolicies []filterconfig.FilterPolicy `yaml:"filter_policies,omitempty" json:"filter_policies,omitempty"`
|
|
EnableClientServerPrefix *bool `yaml:"enable_client_server_prefix,omitempty" json:"enable_client_server_prefix,omitempty"`
|
|
EnableMessagingSystemLatencyHistogram *bool `yaml:"enable_messaging_system_latency_histogram,omitempty" json:"enable_messaging_system_latency_histogram,omitempty"`
|
|
EnableVirtualNodeLabel *bool `yaml:"enable_virtual_node_label,omitempty" json:"enable_virtual_node_label,omitempty"`
|
|
SpanMultiplierKey string `yaml:"span_multiplier_key,omitempty" json:"span_multiplier_key,omitempty"`
|
|
}
|
|
|
|
type SpanMetricsOverrides struct {
|
|
HistogramBuckets []float64 `yaml:"histogram_buckets,omitempty" json:"histogram_buckets,omitempty"`
|
|
Dimensions []string `yaml:"dimensions,omitempty" json:"dimensions,omitempty"`
|
|
IntrinsicDimensions map[string]bool `yaml:"intrinsic_dimensions,omitempty" json:"intrinsic_dimensions,omitempty"`
|
|
FilterPolicies []filterconfig.FilterPolicy `yaml:"filter_policies,omitempty" json:"filter_policies,omitempty"`
|
|
DimensionMappings []sharedconfig.DimensionMappings `yaml:"dimension_mappings,omitempty" json:"dimension_mapings,omitempty"`
|
|
EnableTargetInfo *bool `yaml:"enable_target_info,omitempty" json:"enable_target_info,omitempty"`
|
|
TargetInfoExcludedDimensions []string `yaml:"target_info_excluded_dimensions,omitempty" json:"target_info_excluded_dimensions,omitempty"`
|
|
EnableInstanceLabel *bool `yaml:"enable_instance_label,omitempty" json:"enable_instance_label,omitempty"`
|
|
SpanMultiplierKey string `yaml:"span_multiplier_key,omitempty" json:"span_multiplier_key,omitempty"`
|
|
}
|
|
|
|
type LocalBlocksOverrides struct {
|
|
MaxLiveTraces uint64 `yaml:"max_live_traces,omitempty" json:"max_live_traces,omitempty"`
|
|
MaxBlockDuration time.Duration `yaml:"max_block_duration,omitempty" json:"max_block_duration,omitempty"`
|
|
MaxBlockBytes uint64 `yaml:"max_block_bytes,omitempty" json:"max_block_bytes,omitempty"`
|
|
FlushCheckPeriod time.Duration `yaml:"flush_check_period,omitempty" json:"flush_check_period,omitempty"`
|
|
TraceIdlePeriod time.Duration `yaml:"trace_idle_period,omitempty" json:"trace_idle_period,omitempty"`
|
|
CompleteBlockTimeout time.Duration `yaml:"complete_block_timeout,omitempty" json:"complete_block_timeout,omitempty"`
|
|
}
|
|
|
|
type HostInfoOverrides struct {
|
|
HostIdentifiers []string `yaml:"host_identifiers,omitempty" json:"host_identifies,omitempty"`
|
|
MetricName string `yaml:"metric_name,omitempty" json:"metric_name,omitempty"`
|
|
}
|
|
|
|
type ProcessorOverrides struct {
|
|
ServiceGraphs ServiceGraphsOverrides `yaml:"service_graphs,omitempty" json:"service_graphs,omitempty"`
|
|
SpanMetrics SpanMetricsOverrides `yaml:"span_metrics,omitempty" json:"span_metrics,omitempty"`
|
|
LocalBlocks LocalBlocksOverrides `yaml:"local_blocks,omitempty" json:"local_blocks,omitempty"`
|
|
HostInfo HostInfoOverrides `yaml:"host_info,omitempty" json:"host_info,omitempty"`
|
|
}
|
|
|
|
type RemoteWriteHeaders map[string]config.Secret
|
|
|
|
func (h *RemoteWriteHeaders) toStringStringMap() map[string]string {
|
|
if h == nil {
|
|
return nil
|
|
}
|
|
|
|
headers := make(map[string]string)
|
|
for k, v := range *h {
|
|
headers[k] = string(v)
|
|
}
|
|
return headers
|
|
}
|
|
|
|
type MetricsGeneratorOverrides struct {
|
|
RingSize int `yaml:"ring_size,omitempty" json:"ring_size,omitempty"`
|
|
Processors listtomap.ListToMap `yaml:"processors,omitempty" json:"processors,omitempty"`
|
|
MaxActiveSeries uint32 `yaml:"max_active_series,omitempty" json:"max_active_series,omitempty"`
|
|
MaxActiveEntities uint32 `yaml:"max_active_entities,omitempty" json:"max_active_entities,omitempty"`
|
|
CollectionInterval time.Duration `yaml:"collection_interval,omitempty" json:"collection_interval,omitempty"`
|
|
DisableCollection bool `yaml:"disable_collection,omitempty" json:"disable_collection,omitempty"`
|
|
GenerateNativeHistograms histograms.HistogramMethod `yaml:"generate_native_histograms" json:"generate_native_histograms,omitempty"`
|
|
TraceIDLabelName string `yaml:"trace_id_label_name,omitempty" json:"trace_id_label_name,omitempty"`
|
|
|
|
RemoteWriteHeaders RemoteWriteHeaders `yaml:"remote_write_headers,omitempty" json:"remote_write_headers,omitempty"`
|
|
|
|
Forwarder ForwarderOverrides `yaml:"forwarder,omitempty" json:"forwarder,omitempty"`
|
|
Processor ProcessorOverrides `yaml:"processor,omitempty" json:"processor,omitempty"`
|
|
IngestionSlack time.Duration `yaml:"ingestion_time_range_slack" json:"ingestion_time_range_slack,omitempty"`
|
|
|
|
NativeHistogramBucketFactor float64 `yaml:"native_histogram_bucket_factor,omitempty" json:"native_histogram_bucket_factor,omitempty"`
|
|
NativeHistogramMaxBucketNumber uint32 `yaml:"native_histogram_max_bucket_number,omitempty" json:"native_histogram_max_bucket_number,omitempty"`
|
|
NativeHistogramMinResetDuration time.Duration `yaml:"native_histogram_min_reset_duration,omitempty" json:"native_histogram_min_reset_duration,omitempty"`
|
|
SpanNameSanitization string `yaml:"span_name_sanitization,omitempty" json:"span_name_sanitization,omitempty"`
|
|
MaxCardinalityPerLabel uint64 `yaml:"max_cardinality_per_label,omitempty" json:"max_cardinality_per_label,omitempty"`
|
|
}
|
|
|
|
type ReadOverrides struct {
|
|
// Querier and Ingester enforced overrides.
|
|
MaxBytesPerTagValuesQuery int `yaml:"max_bytes_per_tag_values_query,omitempty" json:"max_bytes_per_tag_values_query,omitempty"`
|
|
MaxBlocksPerTagValuesQuery int `yaml:"max_blocks_per_tag_values_query,omitempty" json:"max_blocks_per_tag_values_query,omitempty"`
|
|
|
|
// QueryFrontend enforced overrides
|
|
MaxSearchDuration model.Duration `yaml:"max_search_duration,omitempty" json:"max_search_duration,omitempty"`
|
|
MaxMetricsDuration model.Duration `yaml:"max_metrics_duration,omitempty" json:"max_metrics_duration,omitempty"`
|
|
|
|
UnsafeQueryHints bool `yaml:"unsafe_query_hints,omitempty" json:"unsafe_query_hints,omitempty"`
|
|
|
|
// LeftPadTraceIDs left-pads trace IDs in search responses to 32 hex characters with zeros.
|
|
// This produces W3C/OpenTelemetry compliant trace IDs (32-hex-character lowercase strings).
|
|
LeftPadTraceIDs bool `yaml:"left_pad_trace_ids,omitempty" json:"left_pad_trace_ids,omitempty"`
|
|
}
|
|
|
|
type CompactionOverrides struct {
|
|
// Backend-worker/scheduler enforced overrides.
|
|
BlockRetention model.Duration `yaml:"block_retention,omitempty" json:"block_retention,omitempty"`
|
|
CompactionWindow model.Duration `yaml:"compaction_window,omitempty" json:"compaction_window,omitempty"`
|
|
CompactionDisabled bool `yaml:"compaction_disabled,omitempty" json:"compaction_disabled,omitempty"`
|
|
}
|
|
|
|
type GlobalOverrides struct {
|
|
// MaxBytesPerTrace is enforced in the Ingester, Compactor, Querier (Search). It
|
|
// is not used when doing a trace by id lookup.
|
|
MaxBytesPerTrace int `yaml:"max_bytes_per_trace,omitempty" json:"max_bytes_per_trace,omitempty"`
|
|
}
|
|
|
|
type StorageOverrides struct {
|
|
// tempodb limits
|
|
DedicatedColumns backend.DedicatedColumns `yaml:"parquet_dedicated_columns" json:"parquet_dedicated_columns"`
|
|
}
|
|
|
|
type CostAttributionOverrides struct {
|
|
MaxCardinality uint64 `yaml:"max_cardinality,omitempty" json:"max_cardinality,omitempty"`
|
|
Dimensions map[string]string `yaml:"dimensions,omitempty" json:"dimensions,omitempty"`
|
|
}
|
|
|
|
type Overrides struct {
|
|
// Ingestion enforced overrides.
|
|
Ingestion IngestionOverrides `yaml:"ingestion,omitempty" json:"ingestion,omitempty"`
|
|
// Read enforced overrides.
|
|
Read ReadOverrides `yaml:"read,omitempty" json:"read,omitempty"`
|
|
// Compaction enforced overrides.
|
|
Compaction CompactionOverrides `yaml:"compaction,omitempty" json:"compaction,omitempty"`
|
|
// MetricsGenerator enforced overrides.
|
|
MetricsGenerator MetricsGeneratorOverrides `yaml:"metrics_generator,omitempty" json:"metrics_generator,omitempty"`
|
|
// Forwarders
|
|
Forwarders []string `yaml:"forwarders,omitempty" json:"forwarders,omitempty"`
|
|
// Global enforced overrides.
|
|
Global GlobalOverrides `yaml:"global,omitempty" json:"global,omitempty"`
|
|
// Storage enforced overrides.
|
|
Storage StorageOverrides `yaml:"storage,omitempty" json:"storage,omitempty"`
|
|
CostAttribution CostAttributionOverrides `yaml:"cost_attribution,omitempty" json:"cost_attribution,omitempty"`
|
|
}
|
|
|
|
type Config struct {
|
|
Defaults Overrides `yaml:"defaults,omitempty" json:"defaults,omitempty"`
|
|
|
|
// Configuration for overrides module
|
|
PerTenantOverrideConfig string `yaml:"per_tenant_override_config" json:"per_tenant_override_config"`
|
|
PerTenantOverridePeriod model.Duration `yaml:"per_tenant_override_period" json:"per_tenant_override_period"`
|
|
|
|
UserConfigurableOverridesConfig UserConfigurableOverridesConfig `yaml:"user_configurable_overrides" json:"user_configurable_overrides"`
|
|
|
|
ConfigType ConfigType `yaml:"-" json:"-"`
|
|
ExpandEnv bool `yaml:"-" json:"-"`
|
|
}
|
|
|
|
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
// Note: this implementation relies on callers using yaml.UnmarshalStrict. In non-strict mode
|
|
// unmarshal() will not return an error for legacy configuration and we return immediately.
|
|
|
|
// Try to unmarshal it normally
|
|
type rawConfig Config
|
|
err := unmarshal((*rawConfig)(c))
|
|
if err == nil {
|
|
c.ConfigType = ConfigTypeNew
|
|
return nil
|
|
}
|
|
|
|
// Try to unmarshal inline limits
|
|
type legacyConfig struct {
|
|
DefaultOverrides LegacyOverrides `yaml:",inline"`
|
|
|
|
PerTenantOverrideConfig string `yaml:"per_tenant_override_config"`
|
|
PerTenantOverridePeriod model.Duration `yaml:"per_tenant_override_period"`
|
|
|
|
UserConfigurableOverridesConfig UserConfigurableOverridesConfig `yaml:"user_configurable_overrides"`
|
|
}
|
|
var legacyCfg legacyConfig
|
|
legacyCfg.DefaultOverrides = c.Defaults.toLegacy()
|
|
legacyCfg.PerTenantOverrideConfig = c.PerTenantOverrideConfig
|
|
legacyCfg.PerTenantOverridePeriod = c.PerTenantOverridePeriod
|
|
legacyCfg.UserConfigurableOverridesConfig = c.UserConfigurableOverridesConfig
|
|
|
|
if legacyErr := unmarshal(&legacyCfg); legacyErr != nil {
|
|
return fmt.Errorf("failed to unmarshal config: %w; also failed in legacy format: %w", err, legacyErr)
|
|
}
|
|
|
|
c.Defaults = legacyCfg.DefaultOverrides.toNewLimits()
|
|
c.PerTenantOverrideConfig = legacyCfg.PerTenantOverrideConfig
|
|
c.PerTenantOverridePeriod = legacyCfg.PerTenantOverridePeriod
|
|
c.UserConfigurableOverridesConfig = legacyCfg.UserConfigurableOverridesConfig
|
|
c.ConfigType = ConfigTypeLegacy
|
|
return nil
|
|
}
|
|
|
|
// RegisterFlagsAndApplyDefaults adds the flags required to config this to the given FlagSet
|
|
func (c *Config) RegisterFlagsAndApplyDefaults(f *flag.FlagSet) {
|
|
// Generator
|
|
c.Defaults.MetricsGenerator.GenerateNativeHistograms = histograms.HistogramMethodClassic
|
|
|
|
// Distributor LegacyOverrides
|
|
c.Defaults.Ingestion.RetryInfoEnabled = true // enabled in overrides by default but it's disabled with RetryAfterOnResourceExhausted = 0
|
|
f.StringVar(&c.Defaults.Ingestion.RateStrategy, "distributor.rate-limit-strategy", "local", "Whether the various ingestion rate limits should be applied individually to each distributor instance (local), or evenly shared across the cluster (global).")
|
|
f.IntVar(&c.Defaults.Ingestion.RateLimitBytes, "distributor.ingestion-rate-limit-bytes", 15e6, "Per-user ingestion rate limit in bytes per second.")
|
|
f.IntVar(&c.Defaults.Ingestion.BurstSizeBytes, "distributor.ingestion-burst-size-bytes", 20e6, "Per-user ingestion burst size in bytes. Should be set to the expected size (in bytes) of a single push request.")
|
|
|
|
// Ingester limits
|
|
f.IntVar(&c.Defaults.Ingestion.MaxLocalTracesPerUser, "ingester.max-traces-per-user", 10e3, "Maximum number of active traces per user, per ingester. 0 to disable.")
|
|
f.IntVar(&c.Defaults.Ingestion.MaxGlobalTracesPerUser, "ingester.max-global-traces-per-user", 0, "Maximum number of active traces per user, across the cluster. 0 to disable.")
|
|
f.IntVar(&c.Defaults.Global.MaxBytesPerTrace, "ingester.max-bytes-per-trace", 50e5, "Maximum size of a trace in bytes. 0 to disable.")
|
|
|
|
// Querier limits
|
|
f.IntVar(&c.Defaults.Read.MaxBytesPerTagValuesQuery, "querier.max-bytes-per-tag-values-query", 10e5, "Maximum size of response for a tag-values query. Used mainly to limit large the number of values associated with a particular tag")
|
|
f.IntVar(&c.Defaults.Read.MaxBlocksPerTagValuesQuery, "querier.max-blocks-per-tag-values-query", 0, "Maximum number of blocks to query for a tag-values query. 0 to disable.")
|
|
|
|
// Generator - NativeHistograms config
|
|
f.Float64Var(&c.Defaults.MetricsGenerator.NativeHistogramBucketFactor, "metrics-generator.native-histogram-bucket-factor", 1.1, "The growth factor between buckets for native histograms.")
|
|
_ = (*Uint32Value)(&c.Defaults.MetricsGenerator.NativeHistogramMaxBucketNumber).Set("100")
|
|
f.Var((*Uint32Value)(&c.Defaults.MetricsGenerator.NativeHistogramMaxBucketNumber), "metrics-generator.native-histogram-max-bucket-number", "The maximum number of buckets for native histograms.")
|
|
f.DurationVar(&c.Defaults.MetricsGenerator.NativeHistogramMinResetDuration, "metrics-generator.native-histogram-min-reset-duration", 15*time.Minute, "The minimum duration before a native histogram can be reset.")
|
|
|
|
f.StringVar(&c.PerTenantOverrideConfig, "config.per-user-override-config", "", "File name of per-user Overrides.")
|
|
_ = c.PerTenantOverridePeriod.Set("10s")
|
|
f.Var(&c.PerTenantOverridePeriod, "config.per-user-override-period", "Period with this to reload the Overrides.")
|
|
|
|
c.UserConfigurableOverridesConfig.RegisterFlagsAndApplyDefaults(f)
|
|
}
|
|
|
|
func (c *Config) Describe(ch chan<- *prometheus.Desc) {
|
|
ch <- metricLimitsDesc
|
|
}
|
|
|
|
func (c *Config) Collect(ch chan<- prometheus.Metric) {
|
|
ch <- prometheus.MustNewConstMetric(metricLimitsDesc, prometheus.GaugeValue, float64(c.Defaults.Ingestion.MaxLocalTracesPerUser), MetricMaxLocalTracesPerUser)
|
|
ch <- prometheus.MustNewConstMetric(metricLimitsDesc, prometheus.GaugeValue, float64(c.Defaults.Ingestion.MaxGlobalTracesPerUser), MetricMaxGlobalTracesPerUser)
|
|
ch <- prometheus.MustNewConstMetric(metricLimitsDesc, prometheus.GaugeValue, float64(c.Defaults.Ingestion.RateLimitBytes), MetricIngestionRateLimitBytes)
|
|
ch <- prometheus.MustNewConstMetric(metricLimitsDesc, prometheus.GaugeValue, float64(c.Defaults.Ingestion.BurstSizeBytes), MetricIngestionBurstSizeBytes)
|
|
ch <- prometheus.MustNewConstMetric(metricLimitsDesc, prometheus.GaugeValue, float64(c.Defaults.Read.MaxBytesPerTagValuesQuery), MetricMaxBytesPerTagValuesQuery)
|
|
ch <- prometheus.MustNewConstMetric(metricLimitsDesc, prometheus.GaugeValue, float64(c.Defaults.Read.MaxBlocksPerTagValuesQuery), MetricMaxBlocksPerTagValuesQuery)
|
|
ch <- prometheus.MustNewConstMetric(metricLimitsDesc, prometheus.GaugeValue, float64(c.Defaults.Global.MaxBytesPerTrace), MetricMaxBytesPerTrace)
|
|
ch <- prometheus.MustNewConstMetric(metricLimitsDesc, prometheus.GaugeValue, float64(c.Defaults.Compaction.BlockRetention), MetricBlockRetention)
|
|
ch <- prometheus.MustNewConstMetric(metricLimitsDesc, prometheus.GaugeValue, float64(c.Defaults.MetricsGenerator.MaxActiveSeries), MetricMetricsGeneratorMaxActiveSeries)
|
|
}
|
|
|
|
func HasNativeHistograms(s histograms.HistogramMethod) bool {
|
|
return s == histograms.HistogramMethodNative || s == histograms.HistogramMethodBoth
|
|
}
|
|
|
|
type Uint32Value uint32
|
|
|
|
func (u *Uint32Value) String() string { return fmt.Sprintf("%d", *u) }
|
|
func (u *Uint32Value) Set(s string) error {
|
|
v, err := strconv.ParseUint(s, 10, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*u = Uint32Value(v)
|
|
return nil
|
|
}
|