Skip to content

Commit b5fdc6d

Browse files
add extended runtime test (#4150)
* add extended runtime test * fix build constraints * format * linter * add handle testing on windows * add ci target for extended test, see what happens * rename test key * increase default time * add check * use shorter period settings * add apache tests * first pass at derivatives * add test * use derivatives * make notice * change time * try to fix notice * add log line to watcher * add NaN check * add more debug data * fix math * fix calc errors * fix test build * cleanup * docs, cleanup * use spigot, fixup errors * use spigot, cef, elastic-agent events for fetching pids * oops * fighting with notice * fix ticker, tinker with other things * docs * improve docs * fix gomod * update notice * fix notice * refactor, use epr for package versions * linter * fix linter * fixes for HTTP helpers * use hard-coded versions * fix ctx * fighting with logs * name changes, fighting with logs * add tests, check logs * fix ci issues * clean up * oops * cleanup, rename a few tings * fix tags
1 parent 4509b55 commit b5fdc6d

16 files changed

+1627
-3
lines changed

.buildkite/pipeline.yml

+10
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,16 @@ steps:
223223
provider: "gcp"
224224
machineType: "n1-standard-8"
225225

226+
- label: "Extended runtime leak tests"
227+
key: "extended-integration-tests"
228+
command: ".buildkite/scripts/steps/integration_tests.sh stateful integration:TestForResourceLeaks"
229+
artifact_paths:
230+
- "build/TEST-**"
231+
- "build/diagnostics/*"
232+
agents:
233+
provider: "gcp"
234+
machineType: "n1-standard-8"
235+
226236
- label: "Integration tests"
227237
key: "integration-tests"
228238
command: ".buildkite/scripts/steps/integration_tests.sh stateful"

NOTICE.txt

+30
Original file line numberDiff line numberDiff line change
@@ -5823,6 +5823,36 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
58235823
SOFTWARE.
58245824

58255825

5826+
--------------------------------------------------------------------------------
5827+
Dependency : github.com/sajari/regression
5828+
Version: v1.0.1
5829+
Licence type (autodetected): MIT
5830+
--------------------------------------------------------------------------------
5831+
5832+
Contents of probable licence file $GOMODCACHE/github.com/sajari/regression@v1.0.1/LICENSE:
5833+
5834+
The MIT License (MIT)
5835+
5836+
Copyright (c) 2014 Sajari Pty Ltd
5837+
5838+
Permission is hereby granted, free of charge, to any person obtaining a copy
5839+
of this software and associated documentation files (the "Software"), to deal
5840+
in the Software without restriction, including without limitation the rights
5841+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5842+
copies of the Software, and to permit persons to whom the Software is
5843+
furnished to do so, subject to the following conditions:
5844+
5845+
The above copyright notice and this permission notice shall be included in
5846+
all copies or substantial portions of the Software.
5847+
5848+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5849+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
5850+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
5851+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
5852+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
5853+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
5854+
THE SOFTWARE.
5855+
58265856
--------------------------------------------------------------------------------
58275857
Dependency : github.com/schollz/progressbar/v3
58285858
Version: v3.13.1

dev-tools/mage/pkg.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ func TestPackages(options ...TestPackagesOption) error {
230230
if mg.Verbose() {
231231
fmt.Println(out)
232232
}
233-
return err
233+
return fmt.Errorf("error running package_test.go: %w, stdout: %s", err, out)
234234
}
235235

236236
return nil

docs/test-framework-dev-guide.md

+5
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ We pass a `-test.count` flag along with the name match
117117
We pass a `-test.run` flag along with the names of the tests we want to run in OR
118118
`GOTEST_FLAGS="-test.run ^(TestStandaloneUpgrade|TestFleetManagedUpgrade)$" mage integration:test`
119119

120+
##### Run Extended Runtime Leak Test
121+
The test framework includes a "long running" test to check for resource leaks and stability.
122+
The runtime of the test can be set via the `LONG_TEST_RUNTIME` environment variable.
123+
The test itself can be run via the `integration:TestLongRunningAgentForLeaks` mage target.
124+
120125
##### Limitations
121126
Due to the way the parameters are passed to `devtools.GoTest` the value of the environment variable
122127
is split on space, so not all combination of flags and their values may be correctly split.

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ require (
5050
github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0
5151
github.com/pkg/errors v0.9.1
5252
github.com/rs/zerolog v1.27.0
53+
github.com/sajari/regression v1.0.1
5354
github.com/schollz/progressbar/v3 v3.13.1
5455
github.com/shirou/gopsutil/v3 v3.24.1
5556
github.com/sirupsen/logrus v1.9.3

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
17001700
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
17011701
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
17021702
github.com/sagikazarmark/crypt v0.9.0/go.mod h1:RnH7sEhxfdnPm1z+XMgSLjWTEIjyK4z2dw6+4vHTMuo=
1703+
github.com/sajari/regression v1.0.1 h1:iTVc6ZACGCkoXC+8NdqH5tIreslDTT/bXxT6OmHR5PE=
1704+
github.com/sajari/regression v1.0.1/go.mod h1:NeG/XTW1lYfGY7YV/Z0nYDV/RGh3wxwd1yW46835flM=
17031705
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
17041706
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
17051707
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=

internal/pkg/agent/application/monitoring/v1_monitor.go

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"time"
1616
"unicode"
1717

18+
"github.com/elastic/elastic-agent-libs/logp"
1819
"github.com/elastic/elastic-agent/pkg/component"
1920
"github.com/elastic/elastic-agent/pkg/utils"
2021

@@ -606,6 +607,10 @@ func (b *BeatsMonitor) injectMetricsInput(cfg map[string]interface{}, componentI
606607
},
607608
},
608609
}
610+
dbgLog := logp.L()
611+
for _, comp := range componentList {
612+
dbgLog.Infof("component: %#v\n\t componentData: %#v", comp, comp.Component.String())
613+
}
609614
for unit, binaryName := range componentIDToBinary {
610615
if !isSupportedMetricsBinary(binaryName) {
611616
continue

magefile.go

+11
Original file line numberDiff line numberDiff line change
@@ -1987,6 +1987,14 @@ func (Integration) TestBeatServerless(ctx context.Context, beatname string) erro
19871987
return integRunner(ctx, false, "TestBeatsServerless")
19881988
}
19891989

1990+
func (Integration) TestForResourceLeaks(ctx context.Context) error {
1991+
err := os.Setenv("TEST_LONG_RUNNING", "true")
1992+
if err != nil {
1993+
return fmt.Errorf("error setting TEST_LONG_RUNNING: %w", err)
1994+
}
1995+
return integRunner(ctx, false, "TestLongRunningAgentForLeaks")
1996+
}
1997+
19901998
// TestOnRemote shouldn't be called locally (called on remote host to perform testing)
19911999
func (Integration) TestOnRemote(ctx context.Context) error {
19922000
mg.Deps(Build.TestBinaries)
@@ -2213,6 +2221,9 @@ func createTestRunner(matrix bool, singleTest string, goTestFlags string, batche
22132221
extraEnv["AGENT_KEEP_INSTALLED"] = os.Getenv("AGENT_KEEP_INSTALLED")
22142222
}
22152223

2224+
extraEnv["TEST_LONG_RUNNING"] = os.Getenv("TEST_LONG_RUNNING")
2225+
extraEnv["LONG_TEST_RUNTIME"] = os.Getenv("LONG_TEST_RUNTIME")
2226+
22162227
// these following two env vars are currently not used by anything, but can be used in the future to test beats or
22172228
// other binaries, see https://github.com/elastic/elastic-agent/pull/3258
22182229
binaryName := os.Getenv("TEST_BINARY_NAME")

pkg/testing/fixture.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,10 @@ func (f *Fixture) ExecStatus(ctx context.Context, opts ...process.CmdOption) (Ag
701701
}, uerr))
702702
}
703703

704-
return status, err
704+
if err != nil {
705+
return status, fmt.Errorf("error running command (output: %s): %w", string(out), err)
706+
}
707+
return status, nil
705708
}
706709

707710
// ExecInspect executes to inspect subcommand on the prepared Elastic Agent binary.
@@ -777,7 +780,7 @@ func (f *Fixture) ExecDiagnostics(ctx context.Context, cmd ...string) (string, e
777780
func (f *Fixture) IsHealthy(ctx context.Context, opts ...process.CmdOption) error {
778781
status, err := f.ExecStatus(ctx, opts...)
779782
if err != nil {
780-
return fmt.Errorf("agent status returned and error: %w", err)
783+
return fmt.Errorf("agent status returned an error: %w", err)
781784
}
782785

783786
if status.State != int(cproto.State_HEALTHY) {

pkg/testing/tools/epr.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package tools
6+
7+
import (
8+
"context"
9+
"encoding/json"
10+
"fmt"
11+
"io"
12+
"net/http"
13+
)
14+
15+
const eprProd = "https://epr.elastic.co"
16+
17+
// / PackageSearchResult contains basic info on a package returned by a search
18+
type PackageSearchResult struct {
19+
Name string `json:"name"`
20+
Version string `json:"version"`
21+
Release string `json:"release"`
22+
Path string `json:"path"`
23+
}
24+
25+
// GetLatestPackageRelease returns the version string of the latest package release
26+
func GetLatestPackageRelease(ctx context.Context, packageName string) (string, error) {
27+
endpoint := fmt.Sprintf("%s/search?package=%s&all=false", eprProd, packageName)
28+
req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil)
29+
if err != nil {
30+
return "", fmt.Errorf("error creating HTTP request: %w", err)
31+
}
32+
resp, err := http.DefaultClient.Do(req) //nolint:gosec,nolintlint // it's a test
33+
//create body before we check for errors, easier to format error strings that way
34+
body, errRead := io.ReadAll(resp.Body)
35+
if errRead != nil {
36+
return "", fmt.Errorf("error reading body of HTTP resp: %w", err)
37+
}
38+
resp.Body.Close()
39+
if err != nil {
40+
return "", fmt.Errorf("failed to create search request for EPR (%s): %w", body, err)
41+
}
42+
if resp.StatusCode >= 300 {
43+
return "", fmt.Errorf("bad status code in response from EPR: %d - %s", resp.StatusCode, resp.Status)
44+
}
45+
46+
parsedResp := []PackageSearchResult{}
47+
err = json.Unmarshal(body, &parsedResp)
48+
if err != nil {
49+
return "", fmt.Errorf("error parsing search response: %w", err)
50+
}
51+
// if we set &all=false, we'll get at most one result
52+
if len(parsedResp) < 1 {
53+
return "", fmt.Errorf("no packages matching '%s' found", packageName)
54+
}
55+
56+
return parsedResp[0].Version, nil
57+
}

pkg/testing/tools/estools/elasticsearch.go

+70
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,44 @@ func GetLogsForAgentID(ctx context.Context, client elastictransport.Interface, i
477477
return handleDocsResponse(res)
478478
}
479479

480+
// GetResultsForAgentAndDatastream returns any documents match both the given agent ID and data stream
481+
func GetResultsForAgentAndDatastream(ctx context.Context, client elastictransport.Interface, dataset string, agentID string) (Documents, error) {
482+
indexQuery := map[string]interface{}{
483+
"query": map[string]interface{}{
484+
"bool": map[string]interface{}{
485+
"must": []map[string]interface{}{
486+
{
487+
"match": map[string]interface{}{"data_stream.dataset": dataset},
488+
},
489+
{
490+
"match": map[string]interface{}{"agent.id": agentID},
491+
},
492+
},
493+
},
494+
},
495+
}
496+
497+
var buf bytes.Buffer
498+
err := json.NewEncoder(&buf).Encode(indexQuery)
499+
if err != nil {
500+
return Documents{}, fmt.Errorf("error creating ES query: %w", err)
501+
}
502+
503+
es := esapi.New(client)
504+
res, err := es.Search(
505+
es.Search.WithExpandWildcards("all"),
506+
es.Search.WithBody(&buf),
507+
es.Search.WithTrackTotalHits(true),
508+
es.Search.WithContext(ctx),
509+
es.Search.WithSize(300),
510+
)
511+
if err != nil {
512+
return Documents{}, fmt.Errorf("error performing ES search: %w", err)
513+
}
514+
515+
return handleDocsResponse(res)
516+
}
517+
480518
// GetLogsForDatasetWithContext returns any logs associated with the datastream
481519
func GetLogsForDatasetWithContext(ctx context.Context, client elastictransport.Interface, index string) (Documents, error) {
482520
indexQuery := map[string]interface{}{
@@ -546,6 +584,38 @@ func performQueryForRawQuery(ctx context.Context, queryRaw map[string]interface{
546584
return handleDocsResponse(res)
547585
}
548586

587+
// FindMatchingLogLinesForAgentWithContext returns the matching `message` line field for an agent with the matching ID
588+
func FindMatchingLogLinesForAgentWithContext(ctx context.Context, client elastictransport.Interface, agentID, line string) (Documents, error) {
589+
queryRaw := map[string]interface{}{
590+
"query": map[string]interface{}{
591+
"bool": map[string]interface{}{
592+
"must": []map[string]interface{}{
593+
{
594+
"match_phrase": map[string]interface{}{
595+
"message": line,
596+
},
597+
},
598+
{
599+
"term": map[string]interface{}{
600+
"agent.id": map[string]interface{}{
601+
"value": agentID,
602+
},
603+
},
604+
},
605+
},
606+
},
607+
},
608+
}
609+
610+
var buf bytes.Buffer
611+
err := json.NewEncoder(&buf).Encode(queryRaw)
612+
if err != nil {
613+
return Documents{}, fmt.Errorf("error creating ES query: %w", err)
614+
}
615+
616+
return performQueryForRawQuery(ctx, queryRaw, "logs-elastic_agent*", client)
617+
}
618+
549619
// GetLogsForDatastream returns any logs associated with the datastream
550620
func GetLogsForDatastream(
551621
ctx context.Context,

pkg/testing/tools/slope.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package tools
6+
7+
import (
8+
"time"
9+
10+
"github.com/sajari/regression"
11+
)
12+
13+
// Slope is a slim wrapper around a regression library for calculating rate of change over time in tests.
14+
type Slope struct {
15+
handler *regression.Regression
16+
}
17+
18+
func NewSlope(label string) Slope {
19+
handler := new(regression.Regression)
20+
handler.SetObserved(label)
21+
handler.SetVar(0, "time")
22+
return Slope{handler: handler}
23+
}
24+
25+
// add a datapoint and timestamp to the calculaton.
26+
func (slope Slope) AddDatapoint(count float64, timeSinceStart time.Duration) {
27+
slope.handler.Train(regression.DataPoint(count, []float64{timeSinceStart.Seconds()}))
28+
}
29+
30+
// Run the regression on the supplied data
31+
func (slope Slope) Run() error {
32+
return slope.handler.Run()
33+
}
34+
35+
// return the slope of the regression
36+
func (slope Slope) GetSlope() float64 {
37+
return slope.handler.GetCoeffs()[1]
38+
}
39+
40+
// Formula returns a string representation of the regression formula
41+
func (slope Slope) Formula() string {
42+
return slope.handler.Formula
43+
}
44+
45+
// Debug returns a string representation of the regression, including all datapoints
46+
func (slope Slope) Debug() string {
47+
return slope.handler.String()
48+
}

0 commit comments

Comments
 (0)