bench

package
v3.7.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 24, 2026 License: AGPL-3.0 Imports: 56 Imported by: 0

README

LogQL Benchmark Suite

This directory contains a comprehensive benchmark suite for LogQL, Loki's query language. The suite is designed to generate realistic log data and benchmark various LogQL queries against different storage implementations.

Overview

The LogQL benchmark suite provides tools to:

  1. Generate realistic log data with configurable cardinality, patterns, and time distributions
  2. Store the generated data in different storage formats (currently supports "chunk" and "dataobj" formats)
  3. Run benchmarks against a variety of LogQL queries, including log filtering and metric queries
  4. Compare performance between different storage implementations

Getting Started

Prerequisites
  • Go 1.21 or later
  • At least 2GB of free disk space (for default dataset size)
Generating Test Data

Before running benchmarks, you need to generate test data:

# Generate default dataset (2GB)
make generate

# Generate a custom-sized dataset (e.g., 500MB)
make generate SIZE=524288000

# Generate for a specific tenant
make generate TENANT=my-tenant

The data generation process:

  1. Creates synthetic log data with realistic patterns
  2. Stores the data in multiple storage formats for comparison
  3. Saves a configuration file that describes the generated dataset

The generated dataset is fully reproducible as it uses a fixed random seed. This ensures that benchmark results are comparable across different runs and environments, making it ideal for performance regression testing.

Running Benchmarks

Once data is generated, you can run benchmarks:

# Run all benchmarks
make bench

# List available benchmark queries
make list

# Run benchmarks with interactive UI
make run

# Run with debug output, you need to tail the logs to see the output `tail -f debug.log`
make run-debug

# Stream sample logs from the dataset
make stream

Architecture

Data Generation

The benchmark suite generates synthetic log data with:

  • Configurable label cardinality (clusters, namespaces, services, pods, containers)
  • Realistic log patterns for different applications (nginx, postgres, java, etc.)
  • Time-based patterns including dense intervals with higher log volume
  • Structured metadata and trace context
Storage Implementations

The suite supports multiple storage implementations:

  1. DataObj Store: Stores logs as serialized protocol buffer objects
  2. Chunk Store: Stores logs in compressed chunks similar to Loki's chunk format
Query Types

The benchmark includes various query types:

  • Log filtering queries (e.g., {app="nginx"} |= "error")
  • Metric queries (e.g., rate({app="nginx"}[5m]))
  • Aggregations (e.g., sum by (status_code) (rate({app="nginx"} | json | status_code != "" [5m])))

Extending the Suite

Adding New Application Types

The benchmark suite supports multiple application types, each generating different log formats. Currently, it includes applications like web servers, databases, caches, Nginx, Kubernetes, Prometheus, Grafana, Loki, and more.

To add a new application type:

  1. Open faker.go and add any new data variables needed for your application:

    myAppComponents := []string{
        "component1",
        "component2",
        // ...
    }
    
  2. Add helper methods to the Faker struct if needed:

    // MyAppComponent returns a random component for my application
    func (f *Faker) MyAppComponent() string {
        return myAppComponents[f.rnd.Intn(len(myAppComponents))]
    }
    
  3. Add a new entry to the defaultApplications slice:

    {
        Name: "my-application",
        LogGenerator: func(level string, ts time.Time, f *Faker) string {
            // Generate log line in your desired format (JSON, logfmt, etc.)
            return fmt.Sprintf(
                `level=%s ts=%s component=%s msg="My application log"`,
                level, ts.Format(time.RFC3339), f.MyAppComponent(),
            )
        },
        OTELResource: map[string]string{
            "service_name":    "my-application",
            "service_version": "1.0.0",
            // Add any OpenTelemetry resource attributes
        },
    }
    
  4. The LogGenerator function receives:

    • level: The log level (info, warn, error, etc.)
    • ts: The timestamp for the log entry
    • f: The faker instance for generating random data
  5. The OTELResource map defines OpenTelemetry resource attributes that will be attached to logs as structured metadata.

After adding your application type, it will be automatically included in the generated dataset with the appropriate distribution based on the generator configuration.

Adding New Storage Implementations

To add a new storage implementation:

  1. Implement the Store interface in store.go
  2. Add the new store to the cmd/generate/main.go file
  3. Update the benchmark test to include the new store type
Adding New Query Types

To add new query types:

  1. Modify the GenerateTestCases method in generator_query.go
  2. Add new query patterns that test different aspects of LogQL

Remote Correctness Tests

Compare query results between two live Loki endpoints. Requires metadata from make discover and the remote_correctness build tag.

go test -tags=remote_correctness -v ./pkg/logql/bench \
    -addr-1=http://loki-baseline:3100 \
    -addr-2=http://loki-test:3100 \
    -org-id=my-tenant \
    -username=admin -password=secret \
    -metadata-dir=pkg/logql/bench/testdata

Use -run to filter queries (same as TestStorageEquality).

Troubleshooting

  • If you see "Data directory is empty" errors, run make generate first
  • For memory issues, try generating a smaller dataset with make generate SIZE=524288000
  • For detailed logs, run benchmarks with make run-debug

Performance Profiles

The benchmark suite can generate CPU and memory profiles:

  • CPU profile: cpu.prof
  • Memory profile: mem.prof

These can be analyzed with:

go tool pprof -http=:8080 cpu.prof
go tool pprof -http=:8080 mem.prof

Metadata Discovery

The discover tool runs against a real Loki instance to generate dataset_metadata.json, which maps available streams, formats, keywords, and other characteristics for query template resolution.

Prerequisites

Either get credentials for the Loki instance you want to test against or setup a port forward.

Build the discover binary:

make -C pkg/logql/bench discover/cmd/discover
Running the Discovery Tool
Run against a cloud instance

This is the primary workflow (available via make discover). It reads TSDB indexes directly from S3 for structural discovery (zero API calls) and uses the Grafana Cloud gateway with HTTP basic auth for content probes (keyword detection, field classification).

pkg/logql/bench/discover/cmd/discover \
  --address "$LOKI_ADDR" \
  --username "$LOKI_USERNAME" \
  --password "$LOKI_PASSWORD" \
  --tenant "$LOKI_USERNAME" \
  --storage-type s3 \
  --s3.buckets "$S3_BUCKET" \
  --s3.region "$S3_REGION" \
  --s3.endpoint "$S3_ENDPOINT" \
  --s3.access-key-id "$AWS_ACCESS_KEY_ID" \
  --s3.secret-access-key "$AWS_SECRET_ACCESS_KEY" \
  --table-prefix "$TABLE_PREFIX" \
  --selector 'namespace=~"namespace-1|namespace-2|namespace-3"' \
  --max-streams 500 \
  --concurrency 5 \
  --output pkg/logql/bench/testdata \
  --queries-dir pkg/logql/bench/queries
Via port-forward (alternative)

If you have kubectl access with port-forward permissions, you can bypass the auth gateway and hit the query-frontend directly. Use --tenant to set the X-Scope-OrgID header.

# In a separate terminal:
kubectl port-forward --context ops-eu-south-0 --namespace loki-ops-002 svc/query-frontend 3100:3100

# Then run discover without --username/--password:
pkg/logql/bench/discover/cmd/discover \
  --address http://localhost:3100 \
  --tenant "$LOKI_USERNAME" \
  --storage-type s3 \
  --s3.buckets "$S3_BUCKET" \
  --s3.region "$S3_REGION" \
  --s3.endpoint "$S3_ENDPOINT" \
  --s3.access-key-id "$AWS_ACCESS_KEY_ID" \
  --s3.secret-access-key "$AWS_SECRET_ACCESS_KEY" \
  --table-prefix "$TABLE_PREFIX" \
  --selector 'namespace=~"namespace-1|namespace-2|namespace-3"' \
  --max-streams 500 \
  --concurrency 5 \
  --output pkg/logql/bench/testdata \
  --queries-dir pkg/logql/bench/queries

Flag reference:

Flag Env var Description
--address LOKI_ADDR Loki base URL
--tenant LOKI_ORG_ID X-Scope-OrgID (internal/ops bypass only)
--username LOKI_USERNAME HTTP basic auth username (Grafana Cloud)
--password LOKI_PASSWORD HTTP basic auth password (Grafana Cloud)
--bearer-token LOKI_BEARER_TOKEN Bearer token for Authorization header
--bearer-token-file LOKI_BEARER_TOKEN_FILE File containing bearer token
--output Directory where dataset_metadata.json will be written
--queries-dir Directory containing LogQL query YAML files (default: skip validation)
--max-streams Maximum streams to include (default: 250)
--from / --to Query time range in RFC3339. Defaults to last 24 hours.
--suites Comma-separated validation suites: fast,regression,exhaustive (default: all)
--concurrency Parallel API call limit (default: 5)
--storage-type Object storage backend for TSDB index access: s3, gcs, azure, filesystem
--s3.buckets Comma-separated S3 bucket names
--s3.region AWS region for S3 access
--s3.endpoint S3 endpoint URL
--s3.access-key-id AWS_ACCESS_KEY_ID AWS access key ID for S3
--s3.secret-access-key AWS_SECRET_ACCESS_KEY AWS secret access key for S3
--table-prefix TSDB index table name prefix (must match schema_config in target Loki deployment)
--selector Additional label matchers to scope discovery (e.g. namespace=~"namespace-1|namespace-2")
Interpreting the Output

The tool prints to stderr and produces a Validation Report at the end:

  • Exit code 0: All query templates resolved against the generated metadata.
  • Exit code 1: One or more query templates failed to resolve. See "Failed Queries" section in output.

Validation Report sections:

  1. Resolution Results — summary counts per suite
  2. Failed Queries — each failed query with error message (e.g., "no streams with log format: json")
  3. Unreferenced Bounded Set Members — bounded set entries no query references (candidates for removal)

Documentation

Index

Constants

View Source
const (
	DefaultDataDir = "./data"
)
View Source
const (

	// MetadataVersion is the current version of the dataset metadata format.
	// It is exported so that callers outside the bench package (e.g. the
	// discover pipeline) can validate or set the version field without
	// needing a separate accessor function.
	MetadataVersion = "1.0"
)

Variables

View Source
var (
	// UnwrappableFields are numeric fields that can be used with | unwrap in
	// metric queries. The discover tool issues series queries for each field to
	// find streams that expose these values.
	UnwrappableFields = []string{
		"bytes",
		"duration",
		"duration_ms",
		"offset",
		"partition",
		"rows_affected",
		"size",
		"spans",
		"status",
		"streams",
		"ttl",
	}

	// FilterableKeywords are literal strings commonly appearing in log content.
	// Used for line filter queries like |= "level" or |~ "error". The discover
	// tool tests keyword presence per stream to populate ByKeyword in the output
	// metadata.
	FilterableKeywords = []string{
		"debug",
		"duration",
		"error",
		"failed",
		"level",
		"refused",
	}

	// StructuredMetadataKeys are keys that appear as structured metadata
	// attached to log entries (not as stream labels). The discover tool uses
	// detected_fields responses to find streams that carry these keys.
	// Note: detected_level lives here (not in LabelKeys) because it is
	// injected by Loki as structured metadata, not as an indexed stream label.
	StructuredMetadataKeys = []string{
		"detected_level",
		"pod",
	}

	// LabelKeys are indexed stream labels used for selectors and grouping
	// (by/without). The discover tool issues one series query per key with
	// matcher {key=~".+"} to enumerate streams.
	LabelKeys = []string{
		"cluster",
		"container",
		"level",
		"namespace",
		"pod",
		"service_name",
	}
)

Bounded sets: characteristics common to all datasets These define the set of possible queries and drive targeted series API queries during stream discovery. Each exported variable corresponds to a discovery query dimension used by the discover CLI.

Functions

func CalculateMinRanges added in v3.7.0

func CalculateMinRanges(config *GeneratorConfig) (minRange, minInstantRange time.Duration)

CalculateMinRanges computes the minimum range durations needed for range and instant queries based on the log generation rate for a stream.

MinRange: For range queries that sample multiple points over time - Formula: time needed for 5 samples (with 10-100 logs per sample)

MinInstantRange: For instant queries that sample at a single point in time - Formula: time needed for 10 logs (to ensure capture at single sampling point)

TODO: This function is specific to the generated data. We will need to expand on this concept when parsing real datasets. For the generated data, the algorithm is based on the following: - baseEntries: 10-100 entries per stream (average ~55) - timeSpread: configured time span over which logs are distributed - logRate: baseEntries / timeSpread (logs per second)

func SaveConfig

func SaveConfig(dataDir string, config *GeneratorConfig) error

SaveConfig saves the generator configuration to a file in the data directory

func SaveMetadata added in v3.7.0

func SaveMetadata(dataDir string, metadata *DatasetMetadata) error

SaveMetadata writes metadata to a JSON file in the specified directory

Types

type Batch

type Batch struct {
	Streams []logproto.Stream
}

Batch represents a collection of log streams

func (Batch) Size

func (b Batch) Size() int

Size of batch in bytes including all entries, labels and structured metadata.

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder helps construct test datasets using multiple stores

func NewBuilder

func NewBuilder(dir string, opt Opt, stores ...Store) *Builder

NewBuilder creates a new Builder

func (*Builder) Generate

func (b *Builder) Generate(ctx context.Context, targetSize int64) error

Generate generates and stores the specified amount of data across all stores

type ChunkStore

type ChunkStore struct {
	// contains filtered or unexported fields
}

func NewChunkStore

func NewChunkStore(dir, tenantID string) (*ChunkStore, error)

func NewChunkStoreWithRegisterer added in v3.7.0

func NewChunkStoreWithRegisterer(dir, tenantID string, reg prometheus.Registerer) (*ChunkStore, error)

func (*ChunkStore) Close

func (s *ChunkStore) Close() error

Close flushes any remaining data and closes resources

func (*ChunkStore) Name

func (s *ChunkStore) Name() string

Name implements Store

func (*ChunkStore) Querier

func (s *ChunkStore) Querier() (logql.Querier, error)

Querier implements Store

func (*ChunkStore) Write

func (s *ChunkStore) Write(ctx context.Context, streams []logproto.Stream) error

type DataObjStore

type DataObjStore struct {
	// contains filtered or unexported fields
}

DataObjStore implements Store using the dataobj format

func NewDataObjStore

func NewDataObjStore(dir, tenant string) (*DataObjStore, error)

NewDataObjStore creates a new DataObjStore

func (*DataObjStore) Close

func (s *DataObjStore) Close() error

Close flushes any remaining data and closes resources

func (*DataObjStore) Name

func (s *DataObjStore) Name() string

Name implements Store

func (*DataObjStore) Write

func (s *DataObjStore) Write(_ context.Context, streams []logproto.Stream) error

Write implements Store

type DataObjV2EngineStore added in v3.6.0

type DataObjV2EngineStore struct {
	// contains filtered or unexported fields
}

DataObjV2EngineStore uses the new engine for querying. It assumes the engine can read the "new dataobj format" (e.g. columnar data) from the provided dataDir via a filesystem objstore.Bucket.

func NewDataObjV2EngineStore added in v3.6.0

func NewDataObjV2EngineStore(dir string, tenantID string) (*DataObjV2EngineStore, error)

NewDataObjV2EngineStore creates a new store that uses the v2 dataobj engine.

type DatasetMetadata added in v3.7.0

type DatasetMetadata struct {
	AllSelectors  []string               `json:"all_selectors"`
	ByFormat      map[LogFormat][]string `json:"by_format"`
	ByServiceName map[string][]string    `json:"by_service_name"`
	Statistics    DatasetStatistics      `json:"statistics"`
	TimeRange     TimeRange              `json:"time_range"`
	Version       string                 `json:"version"`

	ByUnwrappableField   map[string][]string                    `json:"by_unwrappable_field"`   // field name -> selectors with that field
	ByDetectedField      map[string][]string                    `json:"by_detected_field"`      // parsable field -> selectors with that field
	ByStructuredMetadata map[string][]string                    `json:"by_structured_metadata"` // metadata key -> selectors with that key
	ByLabelKey           map[string][]string                    `json:"by_label_key"`           // label name -> selectors with that label
	ByKeyword            map[string][]string                    `json:"by_keyword"`             // keyword -> selectors containing that keyword
	MetadataBySelector   map[string]*SerializableStreamMetadata `json:"metadata_by_selector"`   // selector -> stream metadata for range resolution
}

DatasetMetadata contains queryable information about a generated dataset It maps query properties to stream/label patterns

func BuildMetadata added in v3.7.0

func BuildMetadata(config *GeneratorConfig, streamsMeta []StreamMetadata) *DatasetMetadata

BuildMetadata constructs dataset metadata from stream metadata This is called during data generation to create the metadata file

NOTE: Currently uses helper functions (getUnwrappableFields, etc.) that hardcode application knowledge. This is a temporary approach for synthetic datasets. See TODO comments on helper functions for refactoring plan to support real datasets.

func GenerateInMemoryMetadata added in v3.7.0

func GenerateInMemoryMetadata(config *GeneratorConfig) *DatasetMetadata

GenerateInMemoryMetadata creates metadata in memory without file I/O This is useful for tests that need metadata but don't want to generate a full dataset

func LoadMetadata added in v3.7.0

func LoadMetadata(dataDir string) (*DatasetMetadata, error)

LoadMetadata reads metadata from a JSON file in the specified directory

type DatasetStatistics added in v3.7.0

type DatasetStatistics struct {
	Generated        time.Time         `json:"generated"`
	StreamsByFormat  map[LogFormat]int `json:"streams_by_format"`
	StreamsByService map[string]int    `json:"streams_by_service"`
	TotalStreams     int               `json:"total_streams"`
}

DatasetStatistics provides aggregate information about the dataset

type DenseInterval

type DenseInterval struct {
	Start    time.Time
	Duration time.Duration
}

DenseInterval represents a period of high log volume

type Faker

type Faker struct {
	// contains filtered or unexported fields
}

Faker provides methods to generate fake data consistently

func NewFaker

func NewFaker(rnd *rand.Rand) *Faker

NewFaker creates a new faker with the given random source

func (*Faker) AuthAction

func (f *Faker) AuthAction() string

AuthAction returns a random authentication action

func (*Faker) AuthError

func (f *Faker) AuthError() string

AuthError returns a random authentication error

func (*Faker) AuthSuccess

func (f *Faker) AuthSuccess() bool

AuthSuccess returns a random authentication success status

func (*Faker) CacheError

func (f *Faker) CacheError() string

CacheError returns a random cache error

func (*Faker) CacheKey

func (f *Faker) CacheKey() string

CacheKey returns a random cache key

func (*Faker) CacheOp

func (f *Faker) CacheOp() string

CacheOp returns a random cache operation

func (*Faker) CacheSize

func (f *Faker) CacheSize() int

CacheSize returns a random cache size in bytes

func (*Faker) CacheTTL

func (f *Faker) CacheTTL() int

CacheTTL returns a random cache TTL in seconds

func (*Faker) DBError

func (f *Faker) DBError() string

DBError returns a random database error

func (*Faker) Duration

func (f *Faker) Duration() time.Duration

Duration returns a random duration

func (*Faker) Error

func (f *Faker) Error() string

Error returns a random general error message

func (*Faker) ErrorMessage

func (f *Faker) ErrorMessage() string

ErrorMessage returns a random error message

func (*Faker) GRPCMethod

func (f *Faker) GRPCMethod() string

GRPCMethod returns a random gRPC method

func (*Faker) GrafanaComponent

func (f *Faker) GrafanaComponent() string

GrafanaComponent returns a random Grafana component

func (*Faker) GrafanaLogger

func (f *Faker) GrafanaLogger() string

GrafanaLogger returns a random Grafana logger

func (*Faker) GrafanaMessage

func (f *Faker) GrafanaMessage() string

GrafanaMessage returns a random Grafana message

func (*Faker) Hostname

func (f *Faker) Hostname() string

Hostname returns a random hostname

func (*Faker) IP

func (f *Faker) IP() string

IP returns a random IP address

func (*Faker) K8sComponent

func (f *Faker) K8sComponent() string

K8sComponent returns a random Kubernetes component

func (*Faker) K8sLogPrefix

func (f *Faker) K8sLogPrefix() string

K8sLogPrefix returns a random Kubernetes log prefix

func (*Faker) K8sMessage

func (f *Faker) K8sMessage() string

K8sMessage returns a random Kubernetes message

func (*Faker) KafkaError

func (f *Faker) KafkaError() string

KafkaError returns a random Kafka error

func (*Faker) KafkaEvent

func (f *Faker) KafkaEvent() string

KafkaEvent returns a random Kafka event

func (*Faker) KafkaOffset

func (f *Faker) KafkaOffset() int

KafkaOffset returns a random Kafka offset

func (*Faker) KafkaPartition

func (f *Faker) KafkaPartition() int

KafkaPartition returns a random Kafka partition

func (*Faker) KafkaTopic

func (f *Faker) KafkaTopic() string

KafkaTopic returns a random Kafka topic

func (*Faker) Method

func (f *Faker) Method() string

Method returns a random HTTP method

func (*Faker) NginxErrorType

func (f *Faker) NginxErrorType() string

NginxErrorType returns a random nginx error type

func (*Faker) NginxPath

func (f *Faker) NginxPath() string

NginxPath returns a random nginx path

func (*Faker) OrgID

func (f *Faker) OrgID() string

OrgID returns a random organization ID

func (*Faker) PID

func (f *Faker) PID() int

PID returns a random process ID

func (*Faker) Path

func (f *Faker) Path() string

Path returns a random API path

func (*Faker) PrometheusComponent

func (f *Faker) PrometheusComponent() string

PrometheusComponent returns a random Prometheus component

func (*Faker) PrometheusMessage

func (f *Faker) PrometheusMessage() string

PrometheusMessage returns a random Prometheus message

func (*Faker) PrometheusSubcomponent

func (f *Faker) PrometheusSubcomponent() string

PrometheusSubcomponent returns a random Prometheus subcomponent

func (*Faker) QueryType

func (f *Faker) QueryType() string

QueryType returns a random database query type

func (*Faker) Referer

func (f *Faker) Referer() string

Referer returns a random referer URL

func (*Faker) RowsAffected

func (f *Faker) RowsAffected() int

RowsAffected returns a random number of rows affected

func (*Faker) SpanID

func (f *Faker) SpanID() string

SpanID returns a random span ID

func (*Faker) Status

func (f *Faker) Status() int

Status returns a random HTTP status code

func (*Faker) SyslogPriority

func (f *Faker) SyslogPriority(isError bool) int

SyslogPriority returns a random syslog priority

func (*Faker) Table

func (f *Faker) Table() string

Table returns a random database table name

func (*Faker) TempoComponent

func (f *Faker) TempoComponent() string

TempoComponent returns a random Tempo component

func (*Faker) TempoMessage

func (f *Faker) TempoMessage() string

TempoMessage returns a random Tempo message

func (*Faker) TraceID

func (f *Faker) TraceID() string

TraceID returns a random trace ID

func (*Faker) User

func (f *Faker) User() string

User returns a random username

func (*Faker) UserAgent

func (f *Faker) UserAgent() string

UserAgent returns a random user agent string

type Generator

type Generator struct {
	StreamsMeta []StreamMetadata // Pre-generated stream metadata used across batches
	// contains filtered or unexported fields
}

Generator represents a log generator with configuration

func NewGenerator

func NewGenerator(opt Opt) *Generator

NewGenerator creates a new generator with the given options

func (*Generator) Batches

func (g *Generator) Batches() iter.Seq[*Batch]

Batches returns an iterator that produces log batches Each batch contains the configured number of streams with generated log entries

func (*Generator) GenerateDataset

func (g *Generator) GenerateDataset(targetSize int64, outputFile string) error

GenerateDataset generates a dataset of approximately the specified size

type GeneratorConfig

type GeneratorConfig struct {
	StartTime  time.Time
	TimeSpread time.Duration
	// DenseIntervals defines periods of high log density
	// Each interval will have 10x more logs than normal periods
	DenseIntervals []DenseInterval
	LabelConfig    LabelConfig
	NumStreams     int   // Number of streams to generate per batch
	Seed           int64 // Source of randomness
}

GeneratorConfig contains all configuration for the log generator

func LoadConfig

func LoadConfig(dataDir string) (*GeneratorConfig, error)

LoadConfig loads the generator configuration from the data directory

func (*GeneratorConfig) NewRand

func (c *GeneratorConfig) NewRand() *rand.Rand

NewRand creates a new random source using the configured seed

type K6TestCase added in v3.7.0

type K6TestCase struct {
	Name      string `json:"name"`
	Query     string `json:"query"`
	TenantID  int    `json:"tenantId"`
	Start     string `json:"start"`
	End       string `json:"end"`
	Step      string `json:"step"`
	Direction string `json:"direction"`
	Kind      string `json:"kind"`
	Source    string `json:"source"`
}

K6TestCase represents a test case in the format consumed by the k6 test runner.

func ConvertTestCaseToK6 added in v3.7.0

func ConvertTestCaseToK6(tc TestCase, tenantID int, length time.Duration, buffer time.Duration) K6TestCase

ConvertTestCaseToK6 converts a resolved TestCase into the k6 JSON format. length is the query window duration from the template's TimeRangeConfig. buffer is how far from "now" the query window ends.

type LabelConfig

type LabelConfig struct {
	Clusters    int      // 1-10 clusters
	Namespaces  int      // 10-100 namespaces
	Services    int      // 100-1000 services
	Pods        int      // 1000-10000 pods
	Containers  int      // 1-5 containers per pod
	LogLevels   []string // Log levels to use
	EnvTypes    []string // Environment types
	Regions     []string // Regions
	Datacenters []string // Datacenters
}

LabelConfig configures the cardinality of generated labels

type LogFormat added in v3.7.0

type LogFormat string

LogFormat represents the format of logs produced by an application

const (
	LogFormatJSON         LogFormat = "json"
	LogFormatLogfmt       LogFormat = "logfmt"
	LogFormatUnstructured LogFormat = "unstructured"
)

type LogGenerator

type LogGenerator func(level string, timestamp time.Time, faker *Faker) string

LogGenerator is a function that generates a log line

type MetadataVariableResolver added in v3.7.0

type MetadataVariableResolver struct {
	// contains filtered or unexported fields
}

MetadataVariableResolver resolves variables based on dataset metadata It uses multi-dimensional filtering to ensure queries match compatible streams

func NewMetadataVariableResolver added in v3.7.0

func NewMetadataVariableResolver(metadata *DatasetMetadata, seed int64) *MetadataVariableResolver

NewMetadataVariableResolver creates a new resolver from dataset metadata

func (*MetadataVariableResolver) GetTimeRange added in v3.7.0

func (r *MetadataVariableResolver) GetTimeRange(length time.Duration) (start, end time.Time, err error)

GetTimeRange returns the start and end time for a query based on the metadata time range

func (*MetadataVariableResolver) ResolveQuery added in v3.7.0

func (r *MetadataVariableResolver) ResolveQuery(query string, requirements QueryRequirements, isInstant bool) (string, error)

ResolveQuery resolves variables in a query based on requirements Supports: ${SELECTOR}, ${LABEL_NAME}, ${LABEL_VALUE}, ${RANGE} The isInstant parameter determines whether to use MinInstantRange (true) or MinRange (false) for ${RANGE}

type OTELAttributes

type OTELAttributes struct {
	Resource map[string]string // Resource attributes constant for the service
	Trace    *OTELTraceContext // Optional trace context
}

OTELAttributes represents OpenTelemetry attributes for logs

type OTELTraceContext

type OTELTraceContext struct {
	TraceID string
	SpanID  string
}

OTELTraceContext represents OpenTelemetry trace context

type Opt

type Opt struct {
	// contains filtered or unexported fields
}

Opt represents configuration options for the generator

func DefaultOpt

func DefaultOpt() Opt

DefaultOpt returns the default options

func (Opt) WithDenseInterval

func (o Opt) WithDenseInterval(start time.Time, duration time.Duration) Opt

WithDenseInterval adds a dense interval to the configuration

func (Opt) WithLabelCardinality

func (o Opt) WithLabelCardinality(clusters, namespaces, services, pods, containers int) Opt

WithLabelCardinality configures the cardinality of different labels

func (Opt) WithLabelConfig

func (o Opt) WithLabelConfig(cfg LabelConfig) Opt

WithLabelConfig sets the entire label configuration

func (Opt) WithNumStreams

func (o Opt) WithNumStreams(n int) Opt

WithNumStreams sets the number of streams to generate per batch

func (Opt) WithSeed

func (o Opt) WithSeed(seed int64) Opt

WithSeed sets the seed for random number generation

func (Opt) WithStartTime

func (o Opt) WithStartTime(t time.Time) Opt

WithStartTime sets the start time for log generation

func (Opt) WithTimeSpread

func (o Opt) WithTimeSpread(d time.Duration) Opt

WithTimeSpread sets the time spread for log generation

type QueryDefinition added in v3.7.0

type QueryDefinition struct {
	Description string            `yaml:"description"`
	Query       string            `yaml:"query"`
	Kind        string            `yaml:"kind,omitempty"` // "log" or "metric"
	Skip        bool              `yaml:"skip,omitempty"`
	TimeRange   TimeRangeConfig   `yaml:"time_range"`
	Directions  QueryDirection    `yaml:"directions,omitempty"`
	Requires    QueryRequirements `yaml:"requires,omitempty"` // Requirements for stream selection
	Tags        []string          `yaml:"tags,omitempty"`
	Notes       string            `yaml:"notes,omitempty"`
	Source      string            `yaml:"-"` // Source location (suite/file.yaml:line), populated during load
}

QueryDefinition represents a single query definition from the registry

type QueryDirection added in v3.7.0

type QueryDirection string

QueryDirection specifies which direction(s) to run a log query

const (
	DirectionForward  QueryDirection = "forward"
	DirectionBackward QueryDirection = "backward"
	DirectionBoth     QueryDirection = "both"
)

type QueryFile added in v3.7.0

type QueryFile struct {
	Queries []QueryDefinition `yaml:"queries"`
}

QueryFile represents a YAML file containing query definitions

type QueryRegistry added in v3.7.0

type QueryRegistry struct {
	// contains filtered or unexported fields
}

QueryRegistry manages loading and accessing query definitions

func NewQueryRegistry added in v3.7.0

func NewQueryRegistry(baseDir string) *QueryRegistry

NewQueryRegistry creates a new query registry

func (*QueryRegistry) ExpandQuery added in v3.7.0

func (r *QueryRegistry) ExpandQuery(def QueryDefinition, resolver VariableResolver, isInstant bool) ([]TestCase, error)

ExpandQuery expands a query definition into one or more TestCase instances by resolving variables and creating cases for each direction

func (*QueryRegistry) GetQueries added in v3.7.0

func (r *QueryRegistry) GetQueries(includeSkipped bool, suites ...Suite) []QueryDefinition

GetQueries returns all loaded queries for the specified suites If suites is empty, returns all queries If includeSkipped is false, skipped queries are filtered out

func (*QueryRegistry) Load added in v3.7.0

func (r *QueryRegistry) Load(suites ...Suite) error

Load loads all query definitions for the specified suites

type QueryRequirements added in v3.7.0

type QueryRequirements struct {
	LogFormat          string   `yaml:"log_format,omitempty"`          // "json", "logfmt", or "unstructured"
	UnwrappableFields  []string `yaml:"unwrappable_fields,omitempty"`  // Numeric fields needed for | unwrap
	StructuredMetadata []string `yaml:"structured_metadata,omitempty"` // Structured metadata keys needed
	DetectedFields     []string `yaml:"detected_fields,omitempty"`     // Fields that must be parsable from log content (for aggregations/filters)
	Labels             []string `yaml:"labels,omitempty"`              // Stream label keys needed (for selectors, filters, or grouping)
	Keywords           []string `yaml:"keywords,omitempty"`            // Strings that must appear in log content
}

QueryRequirements specifies what characteristics a stream must have for a query to work

type SerializableStreamMetadata added in v3.7.0

type SerializableStreamMetadata struct {
	MinRange        time.Duration `json:"min_range"`
	MinInstantRange time.Duration `json:"min_instant_range"`
}

SerializableStreamMetadata contains only the fields needed for query resolution This is a subset of StreamMetadata that can be safely serialized to JSON

type Service added in v3.7.0

type Service struct {
	Name         string
	Format       LogFormat // Log format: json, logfmt, or unstructured
	LogGenerator LogGenerator
	OTELResource map[string]string // OTEL resource attributes
}

Service represents a type of application that generates logs

type Store

type Store interface {
	// Write writes a batch of streams to the store
	Write(ctx context.Context, streams []logproto.Stream) error
	// Name returns the name of the store implementation
	Name() string
	// Close flushes any remaining data and closes resources
	Close() error
}

Store represents a storage backend for log data

type StreamMetadata

type StreamMetadata struct {
	Labels  string
	Service Service
	Format  LogFormat // Log format for this stream
	// MinRange is the minimum lookback range for range queries to capture multiple samples
	MinRange time.Duration
	// MinInstantRange is the minimum lookback range for instant queries to capture logs at a single sampling point
	MinInstantRange time.Duration
}

StreamMetadata holds the consistent properties of a stream

type Suite added in v3.7.0

type Suite string

Suite represents a test suite level

const (
	SuiteFast       Suite = "fast"
	SuiteRegression Suite = "regression"
	SuiteExhaustive Suite = "exhaustive"
)

type TestCase

type TestCase struct {
	Query     string
	Start     time.Time
	End       time.Time
	Direction logproto.Direction
	Step      time.Duration // Step size for metric queries
	Source    string        // Source location (suite/file.yaml:line)
	QueryDesc string        // Query description from YAML
	Tags      []string
}

TestCase represents a LogQL test case for benchmarking and testing

func (TestCase) Description

func (c TestCase) Description() string

Description returns a detailed description of the test case including time range

func (TestCase) Equal added in v3.7.0

func (c TestCase) Equal(other TestCase) bool

Equal returns true if two TestCases represent the same query execution.

func (TestCase) Kind added in v3.6.0

func (c TestCase) Kind() string

Kind returns the kind of the test case based on the query type.

func (TestCase) Name

func (c TestCase) Name() string

Name returns a descriptive name for the test case. For log queries, it includes the direction. For metric queries (rate, sum), it returns the query with step size.

type TimeRange added in v3.7.0

type TimeRange struct {
	Start time.Time `json:"start"`
	End   time.Time `json:"end"`
}

TimeRange represents the temporal bounds of a dataset

type TimeRangeConfig added in v3.7.0

type TimeRangeConfig struct {
	// Length is the duration the query should cover (end - start)
	Length string `yaml:"length"`
	// Step is the step size for metric queries
	Step string `yaml:"step,omitempty"`
}

TimeRangeConfig defines the time range parameters for a query

func (*TimeRangeConfig) ParseLength added in v3.7.0

func (t *TimeRangeConfig) ParseLength() (time.Duration, error)

ParseLength parses the length string into a time.Duration

func (*TimeRangeConfig) ParseStep added in v3.7.0

func (t *TimeRangeConfig) ParseStep() (time.Duration, error)

ParseStep parses the step string into a time.Duration

type VariableResolver added in v3.7.0

type VariableResolver interface {
	// ResolveQuery resolves variables in a query based on requirements
	// The isInstant parameter determines whether to use MinInstantRange (true) or MinRange (false) for ${RANGE}
	ResolveQuery(query string, requirements QueryRequirements, isInstant bool) (string, error)

	// GetTimeRange returns the start and end time for a query based on its time range config
	GetTimeRange(length time.Duration) (start, end time.Time, err error)
}

VariableResolver is responsible for resolving query variables

Directories

Path Synopsis
cmd
bench command
generate command
generate-k6 command
stream command
discover
cmd command
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL