diff --git a/wasp/alert.go b/wasp/alert.go index 0e24a05c5..09fef2f47 100644 --- a/wasp/alert.go +++ b/wasp/alert.go @@ -23,6 +23,9 @@ type AlertChecker struct { grafanaClient *grafana.Client } +// NewAlertChecker creates a new AlertChecker using Grafana configurations from environment variables. +// It retrieves GRAFANA_URL and GRAFANA_TOKEN, ensuring they are set. +// Use this function to set up alert checking in tests. func NewAlertChecker(t *testing.T) *AlertChecker { url := os.Getenv("GRAFANA_URL") if url == "" { @@ -43,7 +46,8 @@ func NewAlertChecker(t *testing.T) *AlertChecker { } } -// AnyAlerts check if any alerts with dashboardUUID have been raised +// AnyAlerts retrieves alert groups from Grafana and checks for alerts matching the specified dashboard UUID and requirement label value. +// It returns the matching alert groups, enabling users to identify and respond to specific alert conditions. func (m *AlertChecker) AnyAlerts(dashboardUUID, requirementLabelValue string) ([]grafana.AlertGroupsResponse, error) { raised := false defer func() { @@ -75,7 +79,9 @@ func (m *AlertChecker) AnyAlerts(dashboardUUID, requirementLabelValue string) ([ return alertGroups, nil } -// CheckDashobardAlerts checks for alerts in the given dashboardUUIDs between from and to times +// CheckDashboardAlerts retrieves alert annotations from a Grafana dashboard within the specified time range. +// It returns the sorted alerts and an error if any alert is in the alerting state. +// Use it to verify the status of dashboard alerts after a run. func CheckDashboardAlerts(grafanaClient *grafana.Client, from, to time.Time, dashboardUID string) ([]grafana.Annotation, error) { annotationType := "alert" alerts, _, err := grafanaClient.GetAnnotations(grafana.AnnotationsQueryParams{ diff --git a/wasp/buffer.go b/wasp/buffer.go index ae9ccdc8c..fe277a333 100644 --- a/wasp/buffer.go +++ b/wasp/buffer.go @@ -7,12 +7,15 @@ type SliceBuffer[T any] struct { Data []T } -// NewSliceBuffer creates new limited capacity slice +// NewSliceBuffer creates a new SliceBuffer with the specified capacity. +// It provides an efficient way to store and manage a fixed number of elements, +// enabling optimized access and manipulation in concurrent and decentralized applications. func NewSliceBuffer[T any](cap int) *SliceBuffer[T] { return &SliceBuffer[T]{Capacity: cap, Data: make([]T, 0)} } -// Append appends T if len <= cap, overrides old data otherwise +// Append adds an element to the SliceBuffer. When the buffer reaches its capacity, it overwrites the oldest item. +// This function is useful for maintaining a fixed-size, circular collection of elements. func (m *SliceBuffer[T]) Append(s T) { if m.Idx >= m.Capacity { m.Idx = 0 diff --git a/wasp/cmd.go b/wasp/cmd.go index ddedbf238..89f3453af 100644 --- a/wasp/cmd.go +++ b/wasp/cmd.go @@ -9,14 +9,16 @@ import ( "github.com/rs/zerolog/log" ) -// ExecCmd executes os command, logging both streams +// ExecCmd executes the provided command string and logs its output. +// It returns an error if the command fails to run or exits with a non-zero status. func ExecCmd(command string) error { return ExecCmdWithStreamFunc(command, func(m string) { log.Info().Str("Text", m).Msg("Command output") }) } -// readStdPipe continuously read a pipe from the command +// readStdPipe reads lines from the provided pipe and sends each line to streamFunc. +// It is used to handle streaming output from command execution, such as stdout and stderr. func readStdPipe(pipe io.ReadCloser, streamFunc func(string)) { scanner := bufio.NewScanner(pipe) scanner.Split(bufio.ScanLines) @@ -28,7 +30,8 @@ func readStdPipe(pipe io.ReadCloser, streamFunc func(string)) { } } -// ExecCmdWithStreamFunc executes command with stream function +// ExecCmdWithStreamFunc runs the specified command and streams its output and error lines +// to the provided outputFunction. It enables real-time handling of command execution output. func ExecCmdWithStreamFunc(command string, outputFunction func(string)) error { c := strings.Split(command, " ") cmd := exec.Command(c[0], c[1:]...) diff --git a/wasp/dashboard/cmd/main.go b/wasp/dashboard/cmd/main.go index da99b1873..842844c37 100644 --- a/wasp/dashboard/cmd/main.go +++ b/wasp/dashboard/cmd/main.go @@ -4,6 +4,8 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/wasp/dashboard" ) +// main creates and deploys a default dashboard using environment variables for configuration. +// It sets up the dashboard without extensions or non-functional requirements, enabling straightforward deployment. func main() { // just default dashboard, no NFRs, no dashboard extensions // see examples/alerts.go for an example extension diff --git a/wasp/dashboard/dashboard.go b/wasp/dashboard/dashboard.go index 5c9a6b96d..4bbc8407b 100644 --- a/wasp/dashboard/dashboard.go +++ b/wasp/dashboard/dashboard.go @@ -56,7 +56,7 @@ type Dashboard struct { builder dashboard.Builder } -// NewDashboard creates new dashboard +// NewDashboard initializes a Dashboard with provided alerts and options, using environment variables for configuration. It prepares the dashboard for deployment and returns the instance or an error if setup fails. func NewDashboard(reqs []WaspAlert, opts []dashboard.Option) (*Dashboard, error) { name := os.Getenv("DASHBOARD_NAME") if name == "" { @@ -93,7 +93,8 @@ func NewDashboard(reqs []WaspAlert, opts []dashboard.Option) (*Dashboard, error) return dash, nil } -// Deploy deploys this dashboard to some Grafana folder +// Deploy uploads the Dashboard to Grafana, creating the folder if necessary. +// It returns the deployed grabana.Dashboard and any encountered error. func (m *Dashboard) Deploy() (*grabana.Dashboard, error) { ctx := context.Background() client := grabana.NewClient(&http.Client{}, m.GrafanaURL, grabana.WithAPIToken(m.GrafanaToken)) @@ -105,7 +106,8 @@ func (m *Dashboard) Deploy() (*grabana.Dashboard, error) { return client.UpsertDashboard(ctx, fo, m.builder) } -// defaultStatWidget creates default Stat widget +// defaultStatWidget creates a standard dashboard stat widget using the specified name, datasource, Prometheus target, and legend. +// It is used to display consistent metrics within dashboard rows. func defaultStatWidget(name, datasourceName, target, legend string) row.Option { return row.WithStat( name, @@ -120,7 +122,8 @@ func defaultStatWidget(name, datasourceName, target, legend string) row.Option { ) } -// defaultLastValueAlertWidget creates default last value alert +// defaultLastValueAlertWidget generates a timeseries.Option for alerting using a WaspAlert. +// It returns the custom alert if provided, otherwise configures a default last-value alert for consistent monitoring in dashboards. func defaultLastValueAlertWidget(a WaspAlert) timeseries.Option { if a.CustomAlert != nil { return a.CustomAlert @@ -143,7 +146,9 @@ func defaultLastValueAlertWidget(a WaspAlert) timeseries.Option { ) } -// defaultLabelValuesVar creates a dashboard variable with All/Multiple options +// defaultLabelValuesVar generates a dashboard variable for the specified name and datasource. +// It enables multiple selections, includes an "All" option, and sorts label values in ascending numerical order. +// Use it to create consistent query variables for dashboard filtering. func defaultLabelValuesVar(name, datasourceName string) dashboard.Option { return dashboard.VariableAsQuery( name, @@ -155,7 +160,8 @@ func defaultLabelValuesVar(name, datasourceName string) dashboard.Option { ) } -// timeSeriesWithAlerts creates timeseries graphs per alert + definition of alert +// timeSeriesWithAlerts creates dashboard options for each WaspAlert, configuring time series panels with alert settings. +// Use it to add alert-specific rows to a dashboard based on provided alert definitions. func timeSeriesWithAlerts(datasourceName string, alertDefs []WaspAlert) []dashboard.Option { dashboardOpts := make([]dashboard.Option, 0) for _, a := range alertDefs { @@ -189,6 +195,9 @@ func timeSeriesWithAlerts(datasourceName string, alertDefs []WaspAlert) []dashbo return dashboardOpts } +// AddVariables generates standard dashboard options for common label variables using the provided datasourceName. +// It includes variables like go_test_name, gen_name, branch, commit, and call_group. +// Use this to easily incorporate these variables into your dashboard configuration. func AddVariables(datasourceName string) []dashboard.Option { opts := []dashboard.Option{ defaultLabelValuesVar("go_test_name", datasourceName), @@ -200,7 +209,8 @@ func AddVariables(datasourceName string) []dashboard.Option { return opts } -// dashboard is internal appendable representation of all Dashboard widgets +// dashboard generates dashboard configuration options based on the specified datasource and alert requirements. +// It is used to set up panels and settings when building a new dashboard. func (m *Dashboard) dashboard(datasourceName string, requirements []WaspAlert) []dashboard.Option { panelQuery := map[string]string{ "branch": `=~"${branch:pipe}"`, @@ -221,7 +231,8 @@ func (m *Dashboard) dashboard(datasourceName string, requirements []WaspAlert) [ return defaultOpts } -// Build creates dashboard instance +// Build initializes the Dashboard with the specified name, data source, and alert requirements. +// It prepares the dashboard builder for further configuration and usage. func (m *Dashboard) Build(dashboardName, datasourceName string, requirements []WaspAlert) error { b, err := dashboard.New( dashboardName, @@ -234,12 +245,14 @@ func (m *Dashboard) Build(dashboardName, datasourceName string, requirements []W return nil } -// JSON render dashboard as JSON +// JSON serializes the Dashboard into indented JSON format. +// It provides a human-readable representation, useful for exporting or inspecting the dashboard. func (m *Dashboard) JSON() ([]byte, error) { return m.builder.MarshalIndentJSON() } -// InlineLokiAlertParams is specific params for predefined alerts for wasp dashboard +// InlineLokiAlertParams generates a Loki query string based on the alert type, test name, and generator name. +// It is used to configure specific alert conditions for monitoring test metrics in dashboards. func InlineLokiAlertParams(queryType, testName, genName string) string { switch queryType { case AlertTypeQuantile99: @@ -262,6 +275,8 @@ max_over_time({go_test_name="%s", test_data_type=~"stats", gen_name="%s"} } } +// WASPLoadStatsRow creates a "WASP Load Stats" dashboard row with widgets displaying real-time and total load metrics. +// It utilizes the provided data source and query parameters to configure the relevant statistics for monitoring. func WASPLoadStatsRow(dataSource string, query map[string]string) dashboard.Option { queryString := "" for key, value := range query { @@ -384,6 +399,9 @@ func WASPLoadStatsRow(dataSource string, query map[string]string) dashboard.Opti ) } +// WASPDebugDataRow returns a dashboard.Option containing a row with WASP debug metrics and logs. +// It uses the provided data source and query parameters. +// Use this function to include detailed debug information in your dashboard. func WASPDebugDataRow(dataSource string, query map[string]string, collapse bool) dashboard.Option { queryString := "" for key, value := range query { diff --git a/wasp/dashboard/panels.go b/wasp/dashboard/panels.go index 878f3c7bc..8db760fd1 100644 --- a/wasp/dashboard/panels.go +++ b/wasp/dashboard/panels.go @@ -7,6 +7,9 @@ import ( "github.com/K-Phoen/grabana/timeseries/axis" ) +// RPSPanel creates a time series panel displaying responses per second +// grouped by generator and call group. It is used to monitor +// response rates in the dashboard. func RPSPanel(dataSource string, query map[string]string) row.Option { queryString := "" for key, value := range query { @@ -35,6 +38,8 @@ func RPSPanel(dataSource string, query map[string]string) row.Option { ) } +// RPSVUPerScheduleSegmentsPanel creates a dashboard panel displaying Requests Per Second and Virtual Users segmented by schedule. +// It is used to monitor performance metrics over time for different test configurations. func RPSVUPerScheduleSegmentsPanel(dataSource string, query map[string]string) row.Option { queryString := "" for key, value := range query { diff --git a/wasp/gun_http_mock.go b/wasp/gun_http_mock.go index cffd5fa00..ee15aa203 100644 --- a/wasp/gun_http_mock.go +++ b/wasp/gun_http_mock.go @@ -14,7 +14,8 @@ type MockHTTPGun struct { Data []string } -// NewHTTPMockGun create an HTTP mock gun +// NewHTTPMockGun initializes a MockHTTPGun with the given configuration. +// It sets up the HTTP client and data storage, enabling simulated HTTP interactions for testing. func NewHTTPMockGun(cfg *MockHTTPGunConfig) *MockHTTPGun { return &MockHTTPGun{ client: resty.New(), @@ -23,7 +24,8 @@ func NewHTTPMockGun(cfg *MockHTTPGunConfig) *MockHTTPGun { } } -// Call implements example gun call, assertions on response bodies should be done here +// Call sends an HTTP GET request to the configured target URL and returns the response data. +// It is used to simulate HTTP calls for testing or load generation purposes. func (m *MockHTTPGun) Call(l *Generator) *Response { var result map[string]interface{} r, err := m.client.R(). diff --git a/wasp/gun_sleep_mock.go b/wasp/gun_sleep_mock.go index 2339992c8..8378bbe55 100644 --- a/wasp/gun_sleep_mock.go +++ b/wasp/gun_sleep_mock.go @@ -23,7 +23,8 @@ type MockGun struct { Data []string } -// NewMockGun create a mock gun +// NewMockGun creates a new MockGun instance using the provided configuration. +// It is used to simulate gun behavior for testing or development purposes. func NewMockGun(cfg *MockGunConfig) *MockGun { return &MockGun{ cfg: cfg, @@ -31,7 +32,9 @@ func NewMockGun(cfg *MockGunConfig) *MockGun { } } -// Call implements example gun call, assertions on response bodies should be done here +// Call simulates a request to the Generator. +// Depending on MockGun's configuration, it may succeed, fail, or timeout, +// allowing testing of various response scenarios. func (m *MockGun) Call(l *Generator) *Response { if m.cfg.InternalStop { l.Stop() @@ -54,6 +57,8 @@ func (m *MockGun) Call(l *Generator) *Response { return &Response{Data: "successCallData"} } +// convertResponsesData extracts successful and failed response data from the Generator. +// It returns a slice of successful response strings, OK responses, and failed responses. func convertResponsesData(g *Generator) ([]string, []*Response, []*Response) { g.responsesData.okDataMu.Lock() defer g.responsesData.okDataMu.Unlock() diff --git a/wasp/http_server_mock.go b/wasp/http_server_mock.go index a25eff2d2..1b32b4b70 100644 --- a/wasp/http_server_mock.go +++ b/wasp/http_server_mock.go @@ -18,6 +18,8 @@ type HTTPMockServer struct { Sleep time.Duration } +// Run starts the HTTPMockServer in a separate goroutine. +// It enables the server to handle incoming HTTP requests concurrently. func (s *HTTPMockServer) Run() { go func() { //nolint @@ -25,10 +27,15 @@ func (s *HTTPMockServer) Run() { }() } +// URL returns the base URL of the HTTPMockServer. +// Use it to configure clients to send requests to the mock server during testing. func (s *HTTPMockServer) URL() string { return "http://localhost:8080/1" } +// NewHTTPMockServer initializes an HTTP mock server with configurable latencies and response codes. +// If cfg is nil, default settings are applied. +// Use it to simulate HTTP endpoints for testing purposes. func NewHTTPMockServer(cfg *HTTPMockServerConfig) *HTTPMockServer { if cfg == nil { cfg = &HTTPMockServerConfig{ diff --git a/wasp/k8s.go b/wasp/k8s.go index 9866413bd..8bde6a1cc 100644 --- a/wasp/k8s.go +++ b/wasp/k8s.go @@ -23,7 +23,8 @@ type K8sClient struct { RESTConfig *rest.Config } -// GetLocalK8sDeps get local k8s context config +// GetLocalK8sDeps retrieves the local Kubernetes Clientset and REST configuration. +// It is used to initialize a Kubernetes client for interacting with the cluster. func GetLocalK8sDeps() (*kubernetes.Clientset, *rest.Config, error) { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) @@ -38,7 +39,8 @@ func GetLocalK8sDeps() (*kubernetes.Clientset, *rest.Config, error) { return k8sClient, k8sConfig, nil } -// NewK8sClient creates a new k8s client with a REST config +// NewK8sClient initializes and returns a new K8sClient for interacting with the local Kubernetes cluster. +// It is used to perform operations such as synchronizing groups and managing cluster profiles. func NewK8sClient() *K8sClient { cs, cfg, err := GetLocalK8sDeps() if err != nil { @@ -50,18 +52,27 @@ func NewK8sClient() *K8sClient { } } +// jobPods returns a list of pods in the specified namespace matching the sync label. +// It is used to track and manage job-related pods within Kubernetes environments. func (m *K8sClient) jobPods(ctx context.Context, nsName, syncLabel string) (*v1.PodList, error) { return m.ClientSet.CoreV1().Pods(nsName).List(ctx, metaV1.ListOptions{LabelSelector: syncSelector(syncLabel)}) } +// jobs retrieves the list of Kubernetes jobs within the specified namespace +// that match the provided synchronization label. +// It returns a JobList and an error if the operation fails. func (m *K8sClient) jobs(ctx context.Context, nsName, syncLabel string) (*batchV1.JobList, error) { return m.ClientSet.BatchV1().Jobs(nsName).List(ctx, metaV1.ListOptions{LabelSelector: syncSelector(syncLabel)}) } +// syncSelector formats a sync label into a label selector string. +// It is used to filter Kubernetes jobs and pods based on the specified synchronization label. func syncSelector(s string) string { return fmt.Sprintf("sync=%s", s) } +// removeJobs deletes all jobs in the given JobList within the specified namespace. +// It is used to clean up job resources after they have completed or failed. func (m *K8sClient) removeJobs(ctx context.Context, nsName string, jobs *batchV1.JobList) error { log.Info().Msg("Removing jobs") for _, j := range jobs.Items { @@ -75,6 +86,8 @@ func (m *K8sClient) removeJobs(ctx context.Context, nsName string, jobs *batchV1 return nil } +// waitSyncGroup waits until the specified namespace has jobNum pods with the given syncLabel running. +// It ensures that all required pods are synchronized and operational before proceeding. func (m *K8sClient) waitSyncGroup(ctx context.Context, nsName string, syncLabel string, jobNum int) error { outer: for { @@ -97,7 +110,8 @@ outer: } } -// TrackJobs tracks both jobs and their pods until they succeed or fail +// TrackJobs monitors Kubernetes jobs in the specified namespace and label selector until the desired number succeed or a failure occurs. +// It optionally removes jobs upon completion based on the keepJobs flag. func (m *K8sClient) TrackJobs(ctx context.Context, nsName, syncLabel string, jobNum int, keepJobs bool) error { log.Debug().Str("LabelSelector", syncSelector(syncLabel)).Msg("Searching for jobs/pods") for { diff --git a/wasp/log.go b/wasp/log.go index 887a72e74..60a33b1bb 100644 --- a/wasp/log.go +++ b/wasp/log.go @@ -13,10 +13,13 @@ const ( LogLevelEnvVar = "WASP_LOG_LEVEL" ) +// init initializes the default logging configuration for the package by setting the logging level and output destination. func init() { initDefaultLogging() } +// initDefaultLogging configures the default logger using the LogLevelEnvVar environment variable. +// It sets the logging output to standard error and defaults to the "info" level if the variable is unset. func initDefaultLogging() { lvlStr := os.Getenv(LogLevelEnvVar) if lvlStr == "" { @@ -29,7 +32,9 @@ func initDefaultLogging() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Level(lvl) } -// GetLogger instantiates a logger that takes into account the test context and the log level +// GetLogger returns a zerolog.Logger configured with the specified component name and log level. +// If a *testing.T is provided, the logger integrates with test output. +// Use it to enable consistent logging across components with environment-based log level control. func GetLogger(t *testing.T, componentName string) zerolog.Logger { lvlStr := os.Getenv(LogLevelEnvVar) if lvlStr == "" { diff --git a/wasp/loki_client.go b/wasp/loki_client.go index 5dccec9a9..11a6a8d4c 100644 --- a/wasp/loki_client.go +++ b/wasp/loki_client.go @@ -27,6 +27,8 @@ type LokiLogWrapper struct { client *LokiClient } +// NewLokiLogWrapper initializes a LokiLogWrapper with the specified maximum number of errors. +// It is used to track and limit error occurrences within the LokiClient. func NewLokiLogWrapper(maxErrors int) *LokiLogWrapper { return &LokiLogWrapper{ MaxErrors: maxErrors, @@ -34,10 +36,14 @@ func NewLokiLogWrapper(maxErrors int) *LokiLogWrapper { } } +// SetClient assigns a LokiClient to the LokiLogWrapper. +// Use it to link the log wrapper with a specific Loki client instance for managing log operations. func (m *LokiLogWrapper) SetClient(c *LokiClient) { m.client = c } +// Log processes and forwards log entries to Loki, handling malformed messages and recording errors. +// It ensures error limits are respected and logs at appropriate levels for monitoring purposes. func (m *LokiLogWrapper) Log(kvars ...interface{}) error { if len(m.errors) > m.MaxErrors { return nil @@ -67,7 +73,8 @@ type LokiClient struct { lokiClient.Client } -// Handle handles adding a new label set and a message to the batch +// Handle sends a log entry with the given labels, timestamp, and message to the Loki service. +// It checks error thresholds and returns an error if sending fails or limits are exceeded. func (m *LokiClient) Handle(ls model.LabelSet, t time.Time, s string) error { if m.logWrapper.MaxErrors != -1 && len(m.logWrapper.errors) > m.logWrapper.MaxErrors { return fmt.Errorf("can't send data to Loki, errors: %v", m.logWrapper.errors) @@ -81,7 +88,8 @@ func (m *LokiClient) Handle(ls model.LabelSet, t time.Time, s string) error { return nil } -// HandleStruct handles adding a new label set and a message to the batch, marshalling JSON from struct +// HandleStruct marshals the provided struct to JSON and sends it to Loki with the specified labels and timestamp. +// Use this function to log structured data in a decentralized logging system. func (m *LokiClient) HandleStruct(ls model.LabelSet, t time.Time, st interface{}) error { d, err := json.Marshal(st) if err != nil { @@ -90,7 +98,9 @@ func (m *LokiClient) HandleStruct(ls model.LabelSet, t time.Time, st interface{} return m.Handle(ls, t, string(d)) } -// StopNow stops the client goroutine +// StopNow gracefully terminates the Loki client, ensuring all active streams +// are closed and resources are released. Use this function to stop Loki when +// shutting down services or when Loki is no longer needed. func (m *LokiClient) StopNow() { m.Client.StopNow() } @@ -129,7 +139,8 @@ type LokiConfig struct { MaxLineSizeTruncate bool } -// DefaultLokiConfig is reasonable common settings for Loki batches +// DefaultLokiConfig returns a LokiConfig initialized with default parameters. +// It serves as a base configuration for Loki clients, allowing users to customize settings as needed. func DefaultLokiConfig() *LokiConfig { return &LokiConfig{ MaxErrors: 5, @@ -144,7 +155,7 @@ func DefaultLokiConfig() *LokiConfig { } } -// NewEnvLokiConfig creates new config from connection params as env vars +// NewEnvLokiConfig creates a LokiConfig populated with settings from environment variables. func NewEnvLokiConfig() *LokiConfig { d := DefaultLokiConfig() d.TenantID = os.Getenv("LOKI_TENANT_ID") @@ -154,7 +165,8 @@ func NewEnvLokiConfig() *LokiConfig { return d } -// NewLokiConfig this is used when you have marshalled data from CTF +// NewLokiConfig initializes a LokiConfig with the provided endpoint, tenant, basicAuth, and token. +// Use it to customize connection settings for the Loki logging service. func NewLokiConfig(endpoint *string, tenant *string, basicAuth *string, token *string) *LokiConfig { d := DefaultLokiConfig() if endpoint != nil { @@ -172,7 +184,8 @@ func NewLokiConfig(endpoint *string, tenant *string, basicAuth *string, token *s return d } -// NewLokiClient creates a new Promtail client +// NewLokiClient initializes a new LokiClient with the given LokiConfig. +// It validates the configuration, sets up authentication, and prepares the client for interacting with Loki for logging purposes. func NewLokiClient(extCfg *LokiConfig) (*LokiClient, error) { _, err := http.Get(extCfg.URL) if err != nil { diff --git a/wasp/profile.go b/wasp/profile.go index e2f2d56c5..de81d6739 100644 --- a/wasp/profile.go +++ b/wasp/profile.go @@ -26,7 +26,9 @@ type Profile struct { endTime time.Time } -// Run runs all generators and wait until they finish +// Run executes the profile's generators, manages Grafana annotations, and handles alert checks. +// If wait is true, it waits for all generators to complete before proceeding. +// It returns the updated Profile and any encountered error. func (m *Profile) Run(wait bool) (*Profile, error) { if m.bootstrapErr != nil { return m, m.bootstrapErr @@ -70,6 +72,9 @@ func (m *Profile) printProfileId() { log.Info().Msgf("Profile ID: %s", m.ProfileID) } +// printDashboardLink retrieves the Grafana dashboard URL for the current run +// and logs it. It provides users with a direct link to monitor metrics and alerts +// related to the profile execution. func (m *Profile) printDashboardLink() { if m.grafanaAPI == nil { log.Warn().Msg("Grafana API not set, skipping dashboard link print") @@ -91,6 +96,9 @@ func (m *Profile) printDashboardLink() { log.Info().Msgf("Dashboard URL: %s", url) } +// annotateRunStartOnGrafana posts a run start annotation to the Grafana dashboard. +// It includes run details for monitoring and tracking purposes. +// Logs a warning if the Grafana API is not configured. func (m *Profile) annotateRunStartOnGrafana() { if m.grafanaAPI == nil { log.Warn().Msg("Grafana API not set, skipping annotations") @@ -121,6 +129,9 @@ func (m *Profile) annotateRunStartOnGrafana() { } } +// annotateRunEndOnGrafana creates and posts an end-of-run annotation to Grafana, +// including profile ID, end time, and generator details. +// It is used to mark the completion of a profile run on the Grafana dashboard. func (m *Profile) annotateRunEndOnGrafana() { if m.grafanaAPI == nil { log.Warn().Msg("Grafana API not set, skipping annotations") @@ -151,21 +162,23 @@ func (m *Profile) annotateRunEndOnGrafana() { } } -// Pause pauses execution of all generators +// Pause suspends all generators within the profile. +// It is used to temporarily halt all generator operations managed by the profile. func (m *Profile) Pause() { for _, g := range m.Generators { g.Pause() } } -// Resume resumes execution of all generators +// Resume resumes all generators associated with the profile, allowing them to continue their operations. func (m *Profile) Resume() { for _, g := range m.Generators { g.Resume() } } -// Wait waits until all generators have finished the workload +// Wait blocks until all generators associated with the Profile have finished executing, +// ensuring all operations are complete before proceeding. func (m *Profile) Wait() { for _, g := range m.Generators { g := g @@ -178,7 +191,10 @@ func (m *Profile) Wait() { m.testEndedWg.Wait() } -// NewProfile creates new VU or Gun profile from parts +// NewProfile creates and returns a new Profile instance. +// It initializes the ProfileID with a unique identifier, +// an empty slice of Generators, and a WaitGroup for synchronization. +// Use it to instantiate profiles with default settings. func NewProfile() *Profile { return &Profile{ ProfileID: uuid.NewString()[0:5], @@ -187,6 +203,7 @@ func NewProfile() *Profile { } } +// Add appends a Generator to the Profile. If an error is provided, it records the bootstrap error and does not add the Generator. func (m *Profile) Add(g *Generator, err error) *Profile { if err != nil { m.bootstrapErr = err @@ -204,13 +221,17 @@ type GrafanaOpts struct { CheckDashboardAlertsAfterRun string `toml:"grafana_check_alerts_after_run_on_dashboard_uid"` // Grafana dashboardUID to check for alerts after run } +// WithGrafana configures the Profile with Grafana settings. +// It initializes the Grafana client using the provided options +// and returns the updated Profile instance. func (m *Profile) WithGrafana(opts *GrafanaOpts) *Profile { m.grafanaAPI = grafana.NewGrafanaClient(opts.GrafanaURL, opts.GrafanaToken) m.grafanaOpts = *opts return m } -// waitSyncGroupReady awaits other pods with WASP_SYNC label to start before starting the test +// waitSyncGroupReady waits for the synchronization group to be ready based on environment variables. +// It ensures dependencies are initialized before proceeding with execution. func waitSyncGroupReady() error { if os.Getenv("WASP_NODE_ID") != "" { kc := NewK8sClient() diff --git a/wasp/responses.go b/wasp/responses.go index 33983136c..bf80b10c1 100644 --- a/wasp/responses.go +++ b/wasp/responses.go @@ -14,10 +14,14 @@ type Responses struct { ch chan *Response } +// NewResponses creates a Responses instance using the provided channel. +// It enables concurrent processing and management of Response objects. func NewResponses(ch chan *Response) *Responses { return &Responses{ch} } +// OK sends a successful resty.Response along with its duration and group to the Responses channel. +// It is used to handle and process successful responses in a concurrent environment. func (m *Responses) OK(r *resty.Response, group string) { m.ch <- &Response{ Duration: r.Time(), @@ -26,6 +30,8 @@ func (m *Responses) OK(r *resty.Response, group string) { } } +// Err sends a failed response, including error details and response data, to the Responses channel. +// It is used to handle and propagate errors within the response processing workflow. func (m *Responses) Err(r *resty.Response, group string, err error) { m.ch <- &Response{ Failed: true, diff --git a/wasp/sampler.go b/wasp/sampler.go index 89f43116f..89cc0bdaa 100644 --- a/wasp/sampler.go +++ b/wasp/sampler.go @@ -12,7 +12,9 @@ type Sampler struct { cfg *SamplerConfig } -// NewSampler creates new Sampler +// NewSampler creates a Sampler using the provided SamplerConfig. +// If cfg is nil, a default configuration is applied. +// Use this to initialize sampling behavior for tracking successful call results. func NewSampler(cfg *SamplerConfig) *Sampler { if cfg == nil { cfg = &SamplerConfig{SuccessfulCallResultRecordRatio: 100} @@ -20,7 +22,9 @@ func NewSampler(cfg *SamplerConfig) *Sampler { return &Sampler{cfg: cfg} } -// ShouldRecord return true if we should save CallResult +// ShouldRecord determines whether a Response should be recorded based on its status and the sampler's configuration. +// It updates the provided Stats with the decision. +// Returns true to record the response or false to skip it. func (m *Sampler) ShouldRecord(cr *Response, s *Stats) bool { if cr.Error != "" || cr.Failed || cr.Timeout { s.SamplesRecorded.Add(1) diff --git a/wasp/schedule.go b/wasp/schedule.go index 9cd5d883c..d630b752b 100644 --- a/wasp/schedule.go +++ b/wasp/schedule.go @@ -11,7 +11,8 @@ const ( DefaultStepChangePrecision = 10 ) -// Plain create a constant workload Segment +// Plain creates a slice containing a single Segment starting at `from` with the specified `duration`. +// It is used to initialize basic segments with defined timing. func Plain(from int64, duration time.Duration) []*Segment { return []*Segment{ { @@ -21,7 +22,9 @@ func Plain(from int64, duration time.Duration) []*Segment { } } -// Steps creates a series of increasing/decreasing Segments +// Steps generates a slice of Segment pointers starting from 'from', incremented by 'increase' for each of 'steps' steps. +// Each Segment has a duration equal to the total duration divided by the number of steps. +// Use this function to create uniformly distributed segments over a specified time period. func Steps(from, increase int64, steps int, duration time.Duration) []*Segment { segments := make([]*Segment, 0) perStepDuration := duration / time.Duration(steps) @@ -35,7 +38,8 @@ func Steps(from, increase int64, steps int, duration time.Duration) []*Segment { return segments } -// Combine combines profile segments +// Combine merges multiple slices of Segment pointers into a single slice. +// It is useful for aggregating segment data from various sources. func Combine(segs ...[]*Segment) []*Segment { acc := make([]*Segment, 0) for _, ss := range segs { @@ -44,7 +48,9 @@ func Combine(segs ...[]*Segment) []*Segment { return acc } -// CombineAndRepeat combines and repeats profile segments +// CombineAndRepeat concatenates multiple Segment slices and repeats the combined sequence the specified number of times. +// It returns a single slice containing the repeated segments. +// Panics with ErrNoSchedule if no segments are provided. func CombineAndRepeat(times int, segs ...[]*Segment) []*Segment { if len(segs) == 0 { panic(ErrNoSchedule) diff --git a/wasp/stat.go b/wasp/stat.go index 11ee8842d..3d24589a6 100644 --- a/wasp/stat.go +++ b/wasp/stat.go @@ -19,7 +19,9 @@ var ( var once = &sync.Once{} -// CPUCheckLoop is called once by any generator, makes sense only in cluster runs on Linux +// CPUCheckLoop continuously monitors CPU idle and memory free percentages. +// It terminates the application if resource thresholds are exceeded. +// Use it to ensure the system operates within defined resource limits. func CPUCheckLoop() { once.Do(func() { //nolint diff --git a/wasp/vu_sleep_mock.go b/wasp/vu_sleep_mock.go index bfcf3c99b..2421468b6 100644 --- a/wasp/vu_sleep_mock.go +++ b/wasp/vu_sleep_mock.go @@ -27,7 +27,9 @@ type MockVirtualUser struct { Data []string } -// NewMockVU create a mock virtual user +// NewMockVU creates a new MockVirtualUser with the provided configuration. +// It initializes control structures and prepares data storage. +// Use this function to simulate virtual users for testing decentralized services. func NewMockVU(cfg *MockVirtualUserConfig) *MockVirtualUser { return &MockVirtualUser{ VUControl: NewVUControl(), @@ -36,6 +38,8 @@ func NewMockVU(cfg *MockVirtualUserConfig) *MockVirtualUser { } } +// Clone returns a copy of the MockVirtualUser with a new VUControl and duplicated configuration. +// It is used to create independent virtual user instances for load testing. func (m *MockVirtualUser) Clone(_ *Generator) VirtualUser { return &MockVirtualUser{ VUControl: NewVUControl(), @@ -44,6 +48,8 @@ func (m *MockVirtualUser) Clone(_ *Generator) VirtualUser { } } +// Setup initializes the VirtualUser using the provided Generator. +// It prepares necessary resources and returns an error if the setup process fails. func (m *MockVirtualUser) Setup(_ *Generator) error { if m.cfg.SetupFailure { return errors.New("setup failure") @@ -52,6 +58,8 @@ func (m *MockVirtualUser) Setup(_ *Generator) error { return nil } +// Teardown cleans up the VirtualUser by releasing resources and performing necessary shutdown procedures. +// It returns an error if the teardown process fails, allowing callers to handle cleanup failures appropriately. func (m *MockVirtualUser) Teardown(_ *Generator) error { if m.cfg.TeardownFailure { return errors.New("teardown failure") @@ -60,6 +68,8 @@ func (m *MockVirtualUser) Teardown(_ *Generator) error { return nil } +// Call simulates a virtual user's call to the Generator. +// It sends a Response to the Generator's ResponsesChan, which may indicate success, failure, or timeout based on the mock configuration. func (m *MockVirtualUser) Call(l *Generator) { startedAt := time.Now() time.Sleep(m.cfg.CallSleep) diff --git a/wasp/vu_ws_mock.go b/wasp/vu_ws_mock.go index b7bdf4d98..83cdb62ff 100644 --- a/wasp/vu_ws_mock.go +++ b/wasp/vu_ws_mock.go @@ -21,7 +21,9 @@ type WSMockVU struct { Data []string } -// NewWSMockVU create a ws mock virtual user +// NewWSMockVU initializes a WSMockVU with the provided configuration. +// It sets up control mechanisms and data storage, enabling the simulation +// of a WebSocket virtual user for testing scenarios. func NewWSMockVU(cfg *WSMockVUConfig) *WSMockVU { return &WSMockVU{ VUControl: NewVUControl(), @@ -30,6 +32,8 @@ func NewWSMockVU(cfg *WSMockVUConfig) *WSMockVU { } } +// Clone creates a new VirtualUser instance based on the current WSMockVU. +// It is used to instantiate additional virtual users for scaling load tests. func (m *WSMockVU) Clone(_ *Generator) VirtualUser { return &WSMockVU{ VUControl: NewVUControl(), @@ -38,6 +42,8 @@ func (m *WSMockVU) Clone(_ *Generator) VirtualUser { } } +// Setup establishes a WebSocket connection to the configured target URL using the provided Generator. +// It returns an error if the connection cannot be established, allowing callers to handle setup failures. func (m *WSMockVU) Setup(l *Generator) error { var err error m.conn, _, err = websocket.Dial(context.Background(), m.cfg.TargetURl, &websocket.DialOptions{}) @@ -50,11 +56,14 @@ func (m *WSMockVU) Setup(l *Generator) error { return nil } +// Teardown gracefully closes the WebSocket connection for the VirtualUser. +// It should be called when the user simulation is complete to release resources. func (m *WSMockVU) Teardown(_ *Generator) error { return m.conn.Close(websocket.StatusInternalError, "") } -// Call create a virtual user firing read requests against mock ws server +// Call reads a WebSocket message from the connection and sends the response with a timestamp to the generator's ResponsesChan. +// It is used by a virtual user to handle incoming WebSocket data during execution. func (m *WSMockVU) Call(l *Generator) { startedAt := time.Now() v := map[string]string{} diff --git a/wasp/wasp.go b/wasp/wasp.go index 88cd6c6b3..bc96ab2ef 100644 --- a/wasp/wasp.go +++ b/wasp/wasp.go @@ -62,7 +62,7 @@ type VirtualUser interface { StopChan() chan struct{} } -// NewVUControl creates new base VU that allows us to control the schedule and bring VUs up and down +// NewVUControl creates a new VUControl instance used to manage the lifecycle and control of a virtual user. func NewVUControl() *VUControl { return &VUControl{stop: make(chan struct{}, 1)} } @@ -72,12 +72,13 @@ type VUControl struct { stop chan struct{} } -// Stop stops virtual user execution +// Stop signals VUControl to cease operations by sending a stop signal through the stop channel. func (m *VUControl) Stop(_ *Generator) { m.stop <- struct{}{} } -// StopChan returns stop chan +// StopChan returns the channel used to signal when the VUControl is stopped. +// It allows consumers to listen for termination events and handle cleanup accordingly. func (m *VUControl) StopChan() chan struct{} { return m.stop } @@ -109,6 +110,9 @@ type Segment struct { Duration time.Duration } +// Validate checks that the Segment has a valid starting point and duration. +// It returns an error if the starting point is non-positive or the duration is zero. +// Use it to ensure the Segment is properly configured before processing. func (ls *Segment) Validate() error { if ls.From <= 0 { return ErrStartFrom @@ -145,6 +149,9 @@ type Config struct { nodeID string } +// Validate checks the Config fields for correctness, sets default values for unset parameters, +// and ensures required configurations are provided. It returns an error if the configuration +// is incomplete or invalid, ensuring the Config is ready for use. func (lgc *Config) Validate() error { if lgc.CallTimeout == 0 { lgc.CallTimeout = DefaultCallTimeout @@ -246,8 +253,9 @@ type Generator struct { lokiResponsesChan chan *Response } -// NewGenerator creates a new generator, -// shoots for scheduled RPS until timeout, test logic is defined through Gun or VirtualUser +// NewGenerator initializes a Generator with the provided configuration. +// It validates the config, sets up contexts, logging, and labels. +// Use it to create a Generator for managing service schedules and data collection. func NewGenerator(cfg *Config) (*Generator, error) { if cfg == nil { return nil, ErrNoCfg @@ -326,7 +334,8 @@ func NewGenerator(cfg *Config) (*Generator, error) { return g, nil } -// runExecuteLoop set up initial data for both RPS and VirtualUser load types +// runExecuteLoop initiates the generator's execution loop based on the configured load type. +// It manages request pacing for RPS or handles virtual users for load testing scenarios. func (g *Generator) runExecuteLoop() { g.currentSegment = g.scheduleSegments[0] g.stats.LastSegment.Store(int64(len(g.scheduleSegments))) @@ -360,7 +369,9 @@ func (g *Generator) runExecuteLoop() { } } -// runSetupWithTimeout runs setup with timeout +// runSetupWithTimeout executes the VirtualUser's setup within the configured timeout. +// It returns true if the setup completes successfully before the timeout, otherwise false. +// Use it to ensure that setup processes do not exceed the allowed time. func (g *Generator) runSetupWithTimeout(vu VirtualUser) bool { startedAt := time.Now() ctx, cancel := context.WithTimeout(context.Background(), g.Cfg.SetupTimeout) @@ -383,7 +394,8 @@ func (g *Generator) runSetupWithTimeout(vu VirtualUser) bool { } } -// runTeardownWithTimeout runs teardown with timeout +// runTeardownWithTimeout attempts to teardown the given VirtualUser within the configured timeout. +// It returns true if successful, or false if a timeout or error occurs. func (g *Generator) runTeardownWithTimeout(vu VirtualUser) bool { startedAt := time.Now() ctx, cancel := context.WithTimeout(context.Background(), g.Cfg.TeardownTimeout) @@ -406,7 +418,7 @@ func (g *Generator) runTeardownWithTimeout(vu VirtualUser) bool { } } -// runVU performs virtual user lifecycle +// runVU starts and manages the execution cycle for a VirtualUser. It handles setup, executes user calls with timeout control, processes responses, and ensures proper teardown. Use it to simulate and manage individual virtual user behavior within the Generator. func (g *Generator) runVU(vu VirtualUser) { g.ResponsesWaitGroup.Add(1) go func() { @@ -447,8 +459,8 @@ func (g *Generator) runVU(vu VirtualUser) { }() } -// processSegment change RPS or VUs accordingly -// changing both internal and Stats values to report +// processSegment processes the next schedule segment, updating rate limits or virtual users based on configuration. +// It returns true when all segments have been handled, signaling the scheduler to terminate. func (g *Generator) processSegment() bool { defer func() { g.stats.RunStarted.Store(true) @@ -496,8 +508,8 @@ func (g *Generator) processSegment() bool { return false } -// runScheduleLoop runs scheduling loop -// processing segments inside the whole schedule +// runScheduleLoop initiates an asynchronous loop that processes scheduling segments and monitors for completion signals. +// It enables the generator to handle load distribution seamlessly in the background. func (g *Generator) runScheduleLoop() { go func() { for { @@ -520,7 +532,8 @@ func (g *Generator) runScheduleLoop() { }() } -// storeResponses stores local metrics for responses, pushed them to Loki stream too if Loki is on +// storeResponses processes a Response, updating metrics and recording success or failure. +// It is used to handle generator call results for monitoring and error tracking. func (g *Generator) storeResponses(res *Response) { if g.Cfg.CallTimeout > 0 && res.Duration > g.Cfg.CallTimeout && !res.Timeout { return @@ -561,7 +574,8 @@ func (g *Generator) storeResponses(res *Response) { } } -// collectVUResults collects CallResult from all the VUs +// collectVUResults launches a background process to receive and store virtual user responses. +// It enables asynchronous collection of performance data during load testing. func (g *Generator) collectVUResults() { if g.Cfg.LoadType == RPS { return @@ -586,7 +600,9 @@ func (g *Generator) collectVUResults() { }() } -// pacedCall calls a gun according to a scheduleSegments or plain RPS +// pacedCall initiates a rate-limited request to the external service, +// handling timeouts and storing the response. +// It ensures requests adhere to the generator's configuration and execution state. func (g *Generator) pacedCall() { if !g.Stats().RunStarted.Load() { return @@ -624,7 +640,9 @@ func (g *Generator) pacedCall() { }() } -// Run runs load loop until timeout or stop +// Run starts the Generator’s scheduling and execution workflows, managing logging and metrics. +// If wait is true, it waits for all processes to complete and returns the results. +// Use Run to execute generator tasks either synchronously or asynchronously. func (g *Generator) Run(wait bool) (interface{}, bool) { g.Log.Info().Msg("Load generator started") g.printStatsLoop() @@ -641,20 +659,22 @@ func (g *Generator) Run(wait bool) (interface{}, bool) { return nil, false } -// Pause pauses execution of a generator +// Pause signals the generator to stop its operations. +// It is used to gracefully halt the generator when pausing activities is required. func (g *Generator) Pause() { g.Log.Warn().Msg("Generator was paused") g.stats.RunPaused.Store(true) } -// Resume resumes execution of a generator +// Resume resumes the Generator, allowing it to continue operations after being paused. +// It is typically used to restart paused Generators within a Profile or management structure. func (g *Generator) Resume() { g.Log.Warn().Msg("Generator was resumed") g.stats.RunPaused.Store(false) } -// Stop stops load generator, waiting for all calls for either finish or timeout -// this method is external so Gun/VU implementations can stop the generator +// Stop gracefully halts the generator by updating its run state, logging the event, canceling ongoing responses, and waiting for all processes to complete. +// It returns the final data and a boolean indicating whether the run was successfully stopped. func (g *Generator) Stop() (interface{}, bool) { if g.stats.RunStopped.Load() { return nil, true @@ -667,7 +687,7 @@ func (g *Generator) Stop() (interface{}, bool) { return g.Wait() } -// Wait waits until test ends +// Wait blocks until all generator operations have completed and returns the collected data and a boolean indicating if the run failed. func (g *Generator) Wait() (interface{}, bool) { g.Log.Info().Msg("Waiting for all responses to finish") g.ResponsesWaitGroup.Wait() @@ -681,29 +701,34 @@ func (g *Generator) Wait() (interface{}, bool) { return g.GetData(), g.stats.RunFailed.Load() } -// InputSharedData returns the SharedData passed in Generator config +// InputSharedData retrieves the shared data from the generator's configuration. +// It allows access to common data shared across different components or processes. func (g *Generator) InputSharedData() interface{} { return g.Cfg.SharedData } -// Errors get all calls errors +// Errors returns a slice of error messages collected by the Generator. +// Use this to access all errors encountered during the generation process. func (g *Generator) Errors() []string { return g.errs.Data } -// GetData get all calls data +// GetData retrieves the aggregated response data from the Generator. +// Use it to access all collected responses after processing is complete. func (g *Generator) GetData() *ResponseData { return g.responsesData } -// Stats get all load stats +// Stats returns the current statistics of the Generator. +// It allows callers to access and monitor the generator's state. func (g *Generator) Stats() *Stats { return g.stats } /* Loki's methods to handle CallResult/Stats and stream it to Loki */ -// stopLokiStream stops the Loki stream client +// stopLokiStream gracefully terminates the Loki streaming service if it is configured. +// It ensures that all Loki-related processes are properly stopped. func (g *Generator) stopLokiStream() { if g.Cfg.LokiConfig != nil && g.Cfg.LokiConfig.URL != "" { g.Log.Info().Msg("Stopping Loki") @@ -712,8 +737,8 @@ func (g *Generator) stopLokiStream() { } } -// handleLokiResponsePayload handles CallResult payload with adding default labels -// adding custom CallResult labels if present +// handleLokiResponsePayload enriches a Response with additional labels and submits it to Loki for centralized logging. +// It optimizes the payload by removing unnecessary timestamps and handles any errors that occur during submission. func (g *Generator) handleLokiResponsePayload(r *Response) { labels := g.labels.Merge(model.LabelSet{ "test_data_type": "responses", @@ -731,8 +756,8 @@ func (g *Generator) handleLokiResponsePayload(r *Response) { } } -// handleLokiStatsPayload handles StatsJSON payload with adding default labels -// this stream serves as a debug data and shouldn't be customized with additional labels +// handleLokiStatsPayload transmits the generator’s current statistics to Loki for monitoring. +// It merges relevant labels with the stats data and handles any transmission errors by logging and stopping the generator. func (g *Generator) handleLokiStatsPayload() { ls := g.labels.Merge(model.LabelSet{ "test_data_type": "stats", @@ -744,7 +769,8 @@ func (g *Generator) handleLokiStatsPayload() { } } -// sendResponsesToLoki pushes responses to Loki +// sendResponsesToLoki starts streaming response data to Loki using the generator's configuration. +// It handles incoming responses for monitoring and logging purposes. func (g *Generator) sendResponsesToLoki() { g.Log.Info(). Str("URL", g.Cfg.LokiConfig.URL). @@ -765,7 +791,7 @@ func (g *Generator) sendResponsesToLoki() { }() } -// sendStatsToLoki pushes stats to Loki +// sendStatsToLoki starts a background goroutine that periodically sends generator statistics to Loki for monitoring. func (g *Generator) sendStatsToLoki() { g.dataWaitGroup.Add(1) go func() { @@ -785,7 +811,8 @@ func (g *Generator) sendStatsToLoki() { /* Local logging methods */ -// StatsJSON get all load stats for export +// StatsJSON returns the generator's current statistics as a JSON-compatible map. +// It is used to capture and transmit real-time metrics for monitoring and analysis. func (g *Generator) StatsJSON() map[string]interface{} { return map[string]interface{}{ "node_id": g.Cfg.nodeID, @@ -803,7 +830,9 @@ func (g *Generator) StatsJSON() map[string]interface{} { } } -// printStatsLoop prints stats periodically, with Config.StatsPollInterval +// printStatsLoop starts a background loop that periodically logs generator statistics. +// It runs until the generator's response context is canceled. Use it to monitor +// success, failure, and timeout metrics in real-time. func (g *Generator) printStatsLoop() { g.ResponsesWaitGroup.Add(1) go func() { @@ -825,7 +854,8 @@ func (g *Generator) printStatsLoop() { }() } -// LabelsMapToModel create model.LabelSet from map of labels +// LabelsMapToModel transforms a map of string key-value pairs into a model.LabelSet. +// This enables user-defined labels to be integrated into the model for downstream processing. func LabelsMapToModel(m map[string]string) model.LabelSet { ls := model.LabelSet{} for k, v := range m { diff --git a/wasp/ws_server_mock.go b/wasp/ws_server_mock.go index f146fb1cf..64ff14dfc 100644 --- a/wasp/ws_server_mock.go +++ b/wasp/ws_server_mock.go @@ -17,6 +17,8 @@ type MockWSServer struct { Sleep time.Duration } +// ServeHTTP upgrades the HTTP connection to a WebSocket and continuously sends predefined responses. +// It is used to mock WebSocket server behavior for testing purposes. func (s MockWSServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { c, err := websocket.Accept(w, r, nil) if err != nil { @@ -38,6 +40,8 @@ func (s MockWSServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } +// constantAnswer sends a predefined "epico!" answer to the websocket connection. +// It is used to consistently respond to clients with a fixed message. func constantAnswer(sleep time.Duration, c *websocket.Conn) error { time.Sleep(sleep) return wsjson.Write(context.Background(), c, map[string]string{