<commit_before>package deluge

import (
	"github.com/ofux/deluge-dsl/ast"
	"github.com/ofux/deluge-dsl/evaluator"
	"github.com/ofux/deluge-dsl/object"
	"github.com/ofux/deluge/cleanhttp"
	"github.com/ofux/deluge/deluge/recording"
	log "github.com/sirupsen/logrus"
	"net/http"
	"time"
)

type SimUserStatus int

const (
	Virgin SimUserStatus = iota
	InProgress
	DoneSuccess
	DoneError
)

type SimUser struct {
	Name          string
	scenario      *Scenario
	evaluator     *evaluator.Evaluator
	client        *http.Client
	SleepDuration time.Duration
	httpRecorder  *recording.HTTPRecorder
	log           *log.Entry
	iteration     int

	Status SimUserStatus
	Error  *object.Error
}

func NewSimUser(name string, scenario *Scenario) *SimUser {
	su := &SimUser{
		Name:      name,
		Status:    Virgin,
		scenario:  scenario,
		evaluator: evaluator.NewEvaluator(),
		client:    cleanhttp.DefaultClient(),

		httpRecorder: scenario.httpRecorder,
		log: scenario.log.WithFields(log.Fields{
			"user": name,
		}),
	}

	if err := su.evaluator.AddBuiltin("http", su.ExecHTTPRequest); err != nil {
		log.Fatal(err.Error())
	}

	return su
}

func (su *SimUser) Run(iteration int) {
	su.iteration = iteration
	su.Status = InProgress
	env := object.NewEnvironment()
	evaluated := su.evaluator.Eval(su.scenario.script, env)

	su.client.Transport.(*http.Transport).CloseIdleConnections()

	if evaluated != nil && evaluated.Type() == object.ERROR_OBJ {
		su.Status = DoneError
		su.Error = evaluated.(*object.Error)
		su.log.Errorln(evaluated.Inspect())
		return
	}

	if su.Status == InProgress {
		su.Status = DoneSuccess
	}
}

func (su *SimUser) ExecHTTPRequest(node ast.Node, args ...object.Object) object.Object {
	if oErr := evaluator.AssertArgsType(node, args, object.STRING_OBJ, object.HASH_OBJ); oErr != nil {
		return oErr
	}

	reqName := args[0].(*object.String).Value
	reqObj := args[1].(*object.Hash)

	jsUrl, ok := reqObj.Get("url")
	if !ok {
		return evaluator.NewError(node, "invalid HTTP request: missing 'url' field")
	}
	url, ok := jsUrl.Value.(*object.String)
	if !ok {
		return evaluator.NewError(node, "invalid HTTP request: 'url' should be a STRING")
	}

	var method = "GET"
	if methodField, ok := reqObj.Get("method"); ok {
		if methodFieldVal, ok := methodField.Value.(*object.String); ok {
			method = methodFieldVal.Value
		}
	}

	req, err := http.NewRequest(method, url.Value, nil)
	if err != nil {
		return evaluator.NewError(node, err.Error())
	}

	su.log.Debugf("Performing HTTP request: %s %s", req.Method, req.URL.String())
	start := time.Now()
	res, err := su.client.Do(req)
	end := time.Now()
	duration := end.Sub(start)

	if err != nil {
		su.log.Debugf("Request error: %s", err.Error())
		return evaluator.NewError(node, err.Error())
	} else {
		su.log.Debugf("Response status: %s in %s", "res.Status", duration.String())
		su.httpRecorder.Record(&recording.HTTPRecordEntry{
			Iteration:  su.iteration,
			Name:       reqName,
			Value:      duration.Nanoseconds() / 100000,
			StatusCode: res.StatusCode,
		})
	}

	return evaluator.NULL
}<commit_msg>Fix leak when doing http request by closing the body<commit_after>package deluge

import (
	"github.com/ofux/deluge-dsl/ast"
	"github.com/ofux/deluge-dsl/evaluator"
	"github.com/ofux/deluge-dsl/object"
	"github.com/ofux/deluge/cleanhttp"
	"github.com/ofux/deluge/deluge/recording"
	log "github.com/sirupsen/logrus"
	"net/http"
	"time"
)

type SimUserStatus int

const (
	Virgin SimUserStatus = iota
	InProgress
	DoneSuccess
	DoneError
)

type SimUser struct {
	Name          string
	scenario      *Scenario
	evaluator     *evaluator.Evaluator
	client        *http.Client
	SleepDuration time.Duration
	httpRecorder  *recording.HTTPRecorder
	log           *log.Entry
	iteration     int

	Status SimUserStatus
	Error  *object.Error
}

func NewSimUser(name string, scenario *Scenario) *SimUser {
	su := &SimUser{
		Name:      name,
		Status:    Virgin,
		scenario:  scenario,
		evaluator: evaluator.NewEvaluator(),
		client:    cleanhttp.DefaultClient(),

		httpRecorder: scenario.httpRecorder,
		log: scenario.log.WithFields(log.Fields{
			"user": name,
		}),
	}

	if err := su.evaluator.AddBuiltin("http", su.ExecHTTPRequest); err != nil {
		log.Fatal(err.Error())
	}

	return su
}

func (su *SimUser) Run(iteration int) {
	su.iteration = iteration
	su.Status = InProgress
	env := object.NewEnvironment()
	evaluated := su.evaluator.Eval(su.scenario.script, env)

	su.client.Transport.(*http.Transport).CloseIdleConnections()

	if evaluated != nil && evaluated.Type() == object.ERROR_OBJ {
		su.Status = DoneError
		su.Error = evaluated.(*object.Error)
		su.log.Errorln(evaluated.Inspect())
		return
	}

	if su.Status == InProgress {
		su.Status = DoneSuccess
	}
}

func (su *SimUser) ExecHTTPRequest(node ast.Node, args ...object.Object) object.Object {
	if oErr := evaluator.AssertArgsType(node, args, object.STRING_OBJ, object.HASH_OBJ); oErr != nil {
		return oErr
	}

	reqName := args[0].(*object.String).Value
	reqObj := args[1].(*object.Hash)

	jsUrl, ok := reqObj.Get("url")
	if !ok {
		return evaluator.NewError(node, "invalid HTTP request: missing 'url' field")
	}
	url, ok := jsUrl.Value.(*object.String)
	if !ok {
		return evaluator.NewError(node, "invalid HTTP request: 'url' should be a STRING")
	}

	var method = "GET"
	if methodField, ok := reqObj.Get("method"); ok {
		if methodFieldVal, ok := methodField.Value.(*object.String); ok {
			method = methodFieldVal.Value
		}
	}

	req, err := http.NewRequest(method, url.Value, nil)
	if err != nil {
		return evaluator.NewError(node, err.Error())
	}

	su.log.Debugf("Performing HTTP request: %s %s", req.Method, req.URL.String())
	start := time.Now()
	res, err := su.client.Do(req)
	end := time.Now()
	duration := end.Sub(start)

	if err != nil {
		su.log.Debugf("Request error: %s", err.Error())
		return evaluator.NewError(node, err.Error())
	} else {
		defer res.Body.Close()
		su.log.Debugf("Response status: %s in %s", "res.Status", duration.String())
		su.httpRecorder.Record(&recording.HTTPRecordEntry{
			Iteration:  su.iteration,
			Name:       reqName,
			Value:      duration.Nanoseconds() / 100000,
			StatusCode: res.StatusCode,
		})
	}

	return evaluator.NULL
}
{'fraction_non_alphanumeric': 0.14276506606509828, 'fraction_numerical': 0.002578150177247825, 'mean_word_length': 3.7967542503863987, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7083802', 'n_tokens_mistral': 2350, 'n_tokens_neox': 2237, 'n_words': 473}