Skip to content

Commit

Permalink
Merge pull request #592 from isucon/feat/request-static-files-in-scen…
Browse files Browse the repository at this point in the history
…ario

feat: 負荷走行中にも静的ファイルのリクエストを送る
  • Loading branch information
sapphi-red authored Dec 4, 2024
2 parents 550c7be + 7a1a618 commit d6a7bc1
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 37 deletions.
6 changes: 3 additions & 3 deletions bench/benchmarker/scenario/prevalidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func validateFrontendFiles(ctx context.Context, clientConfig webapp.ClientConfig
indexHtmlHash := frontendHashes["index.html"]

{
actualHash, err := client.StaticGetFileHash(ctx, "/")
actualHash, err := client.StaticGetFileHash(ctx, "/client")
if err != nil {
return err
}
Expand All @@ -76,12 +76,12 @@ func validateFrontendFiles(ctx context.Context, clientConfig webapp.ClientConfig

// check index.html for other paths
{
actualHash, err := client.StaticGetFileHash(ctx, "/client")
actualHash, err := client.StaticGetFileHash(ctx, "/owner")
if err != nil {
return err
}
if actualHash != indexHtmlHash {
return errors.New("/clientの内容が正しくありません")
return errors.New("/ownerの内容が正しくありません")
}
}

Expand Down
2 changes: 1 addition & 1 deletion bench/benchmarker/scenario/scenario.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func NewScenario(target, addr, paymentURL string, logger *slog.Logger, reporter
TargetBaseURL: target,
TargetAddr: addr,
ClientIdleConnTimeout: 10 * time.Second,
})
}, skipStaticFileSanityCheck)
w := world.NewWorld(30*time.Millisecond, completedRequestChan, worldClient, logger)

worldCtx := world.NewContext(w)
Expand Down
4 changes: 4 additions & 0 deletions bench/benchmarker/scenario/worldclient/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const (
ErrorCodeFailedToPostRidesEstimatedFare
// ErrorCodeFailedToGetNearbyChairs 近くの椅子情報の取得に失敗したエラー
ErrorCodeFailedToGetNearbyChairs
// ErrorCodeFailedToGetStaticFile 静的ファイルの取得に失敗したエラー
ErrorCodeFailedToGetStaticFile
// ErrorCodeInvalidContent 静的ファイルの内容が一致しないエラー
ErrorCodeInvalidContent
)

type codeError struct {
Expand Down
108 changes: 87 additions & 21 deletions bench/benchmarker/scenario/worldclient/worldclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ import (
"github.com/isucon/isucon14/bench/benchmarker/webapp"
"github.com/isucon/isucon14/bench/benchmarker/webapp/api"
"github.com/isucon/isucon14/bench/benchmarker/world"
"github.com/isucon/isucon14/bench/benchrun"
)

type userClient struct {
ctx context.Context
client *webapp.Client
ctx context.Context
client *webapp.Client
skipStaticFileSanityCheck bool
}

type ownerClient struct {
ctx context.Context
client *webapp.Client
webappClientConfig webapp.ClientConfig
ctx context.Context
client *webapp.Client
webappClientConfig webapp.ClientConfig
skipStaticFileSanityCheck bool
}

type chairClient struct {
Expand All @@ -31,22 +34,34 @@ type chairClient struct {
}

type WorldClient struct {
ctx context.Context
webappClientConfig webapp.ClientConfig
ctx context.Context
webappClientConfig webapp.ClientConfig
skipStaticFileSanityCheck bool
}

func NewWorldClient(ctx context.Context, webappClientConfig webapp.ClientConfig) *WorldClient {
func NewWorldClient(ctx context.Context, webappClientConfig webapp.ClientConfig, skipStaticFileSanityCheck bool) *WorldClient {
return &WorldClient{
ctx: ctx,
webappClientConfig: webappClientConfig,
ctx: ctx,
webappClientConfig: webappClientConfig,
skipStaticFileSanityCheck: skipStaticFileSanityCheck,
}
}

func (c *WorldClient) RegisterUser(ctx *world.Context, data *world.RegisterUserRequest) (*world.RegisterUserResponse, error) {
func (c *WorldClient) RegisterUser(ctx *world.Context, data *world.RegisterUserRequest, beforeRequest func(client world.UserClient) error) (*world.RegisterUserResponse, error) {
client, err := webapp.NewClient(c.webappClientConfig)
if err != nil {
return nil, WrapCodeError(ErrorCodeFailedToCreateWebappClient, err)
}
userClient := &userClient{
ctx: c.ctx,
client: client,
skipStaticFileSanityCheck: c.skipStaticFileSanityCheck,
}

err = beforeRequest(userClient)
if err != nil {
return nil, err
}

response, err := client.AppPostRegister(c.ctx, &api.AppPostUsersReq{
Username: data.UserName,
Expand All @@ -62,18 +77,26 @@ func (c *WorldClient) RegisterUser(ctx *world.Context, data *world.RegisterUserR
return &world.RegisterUserResponse{
ServerUserID: response.ID,
InvitationCode: response.InvitationCode,
Client: &userClient{
ctx: c.ctx,
client: client,
},
Client: userClient,
}, nil
}

func (c *WorldClient) RegisterOwner(ctx *world.Context, data *world.RegisterOwnerRequest) (*world.RegisterOwnerResponse, error) {
func (c *WorldClient) RegisterOwner(ctx *world.Context, data *world.RegisterOwnerRequest, beforeRequest func(client world.OwnerClient) error) (*world.RegisterOwnerResponse, error) {
client, err := webapp.NewClient(c.webappClientConfig)
if err != nil {
return nil, WrapCodeError(ErrorCodeFailedToCreateWebappClient, err)
}
ownerClient := &ownerClient{
ctx: c.ctx,
client: client,
webappClientConfig: c.webappClientConfig,
skipStaticFileSanityCheck: c.skipStaticFileSanityCheck,
}

err = beforeRequest(ownerClient)
if err != nil {
return nil, err
}

response, err := client.OwnerPostRegister(c.ctx, &api.OwnerPostOwnersReq{
Name: data.Name,
Expand All @@ -85,11 +108,7 @@ func (c *WorldClient) RegisterOwner(ctx *world.Context, data *world.RegisterOwne
return &world.RegisterOwnerResponse{
ServerOwnerID: response.ID,
ChairRegisteredToken: response.ChairRegisterToken,
Client: &ownerClient{
ctx: c.ctx,
client: client,
webappClientConfig: c.webappClientConfig,
},
Client: ownerClient,
}, nil
}

Expand Down Expand Up @@ -169,6 +188,13 @@ func (c *ownerClient) GetOwnerChairs(ctx *world.Context, args *world.GetOwnerCha
})}, nil
}

func (c *ownerClient) BrowserAccess(ctx *world.Context, scenario benchrun.FrontendPathScenario) error {
if c.skipStaticFileSanityCheck {
return nil
}
return browserAccess(c.ctx, c.client, scenario)
}

func (c *chairClient) SendChairCoordinate(ctx *world.Context, chair *world.Chair) (*world.SendChairCoordinateResponse, error) {
response, err := c.client.ChairPostCoordinate(c.ctx, &api.Coordinate{
Latitude: chair.Location.Current().X,
Expand Down Expand Up @@ -267,6 +293,10 @@ func (c *chairClient) ConnectChairNotificationStream(ctx *world.Context, chair *
}, nil
}

func (c *userClient) getInternalClient() *webapp.Client {
return c.client
}

func (c *userClient) SendEvaluation(ctx *world.Context, req *world.Request, score int) (*world.SendEvaluationResponse, error) {
res, err := c.client.AppPostRequestEvaluate(c.ctx, req.ServerID, &api.AppPostRideEvaluationReq{
Evaluation: score,
Expand Down Expand Up @@ -446,3 +476,39 @@ type notificationConnectionImpl struct {
func (c *notificationConnectionImpl) Close() {
c.close()
}

func (c *userClient) BrowserAccess(ctx *world.Context, scenario benchrun.FrontendPathScenario) error {
if c.skipStaticFileSanityCheck {
return nil
}
return browserAccess(c.ctx, c.client, scenario)
}

func browserAccess(ctx context.Context, client *webapp.Client, scenario benchrun.FrontendPathScenario) error {
paths := benchrun.FRONTEND_PATH_SCENARIOS[scenario]
path := paths[len(paths)-1]

// ハードナビゲーション
if len(paths) == 1 {
hash, err := client.StaticGetFileHash(ctx, path)
if err != nil {
return WrapCodeError(ErrorCodeFailedToGetStaticFile, err)
}
if hash != benchrun.FrontendHashesMap["index.html"] {
return WrapCodeError(ErrorCodeInvalidContent, errors.New("invalid content for "+path))
}
}

filePaths := benchrun.FrontendPathScenarioFiles[scenario]
for _, filePath := range filePaths {
hash, err := client.StaticGetFileHash(ctx, filePath)
if err != nil {
return WrapCodeError(ErrorCodeFailedToGetStaticFile, err)
}
if hash != benchrun.FrontendHashesMap[filePath[1:]] {
return WrapCodeError(ErrorCodeInvalidContent, errors.New("invalid content for "+filePath))
}
}

return nil
}
4 changes: 2 additions & 2 deletions bench/benchmarker/webapp/client_static.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ func (c *Client) StaticGetFileHash(ctx context.Context, path string) (string, er
}
defer closeBody(resp)

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("GET %sへのリクエストに対して、期待されたHTTPステータスコードが確認できませんでした (expected:%d, actual:%d)", path, http.StatusOK, resp.StatusCode)
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return "", fmt.Errorf("GET %sへのリクエストに対して、期待されたHTTPステータスコードが確認できませんでした (expected:200~399, actual:%d)", path, resp.StatusCode)
}

hash, err := benchrun.GetHashFromStream(resp.Body)
Expand Down
9 changes: 7 additions & 2 deletions bench/benchmarker/world/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"time"

"github.com/guregu/null/v5"
"github.com/isucon/isucon14/bench/benchrun"
)

type WorldClient interface {
// RegisterUser サーバーにユーザーを登録する
RegisterUser(ctx *Context, data *RegisterUserRequest) (*RegisterUserResponse, error)
RegisterUser(ctx *Context, data *RegisterUserRequest, beforeRequest func(client UserClient) error) (*RegisterUserResponse, error)
// RegisterOwner サーバーにオーナーを登録する
RegisterOwner(ctx *Context, data *RegisterOwnerRequest) (*RegisterOwnerResponse, error)
RegisterOwner(ctx *Context, data *RegisterOwnerRequest, beforeRequest func(client OwnerClient) error) (*RegisterOwnerResponse, error)
// RegisterChair サーバーにユーザーを登録する
RegisterChair(ctx *Context, owner *Owner, data *RegisterChairRequest) (*RegisterChairResponse, error)
}
Expand All @@ -30,13 +31,17 @@ type UserClient interface {
RegisterPaymentMethods(ctx *Context, user *User) error
// ConnectUserNotificationStream ユーザー用の通知ストリームに接続する
ConnectUserNotificationStream(ctx *Context, user *User, receiver NotificationReceiverFunc) (NotificationStream, error)
// BrowserAccess ブラウザでアクセスしたときのリクエストを送信する
BrowserAccess(ctx *Context, scenario benchrun.FrontendPathScenario) error
}

type OwnerClient interface {
// GetOwnerSales サーバーからオーナーの売り上げ情報を取得する
GetOwnerSales(ctx *Context, args *GetOwnerSalesRequest) (*GetOwnerSalesResponse, error)
// GetOwnerChairs サーバーからオーナーの椅子一覧を取得する
GetOwnerChairs(ctx *Context, args *GetOwnerChairsRequest) (*GetOwnerChairsResponse, error)
// BrowserAccess ブラウザでアクセスしたときのリクエストを送信する
BrowserAccess(ctx *Context, scenario benchrun.FrontendPathScenario) error
}

type ChairClient interface {
Expand Down
11 changes: 11 additions & 0 deletions bench/benchmarker/world/owner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sync/atomic"
"time"

"github.com/isucon/isucon14/bench/benchrun"
"github.com/isucon/isucon14/bench/internal/concurrent"
"github.com/samber/lo"
)
Expand Down Expand Up @@ -72,6 +73,11 @@ func (p *Owner) Tick(ctx *Context) error {
defer p.tickDone.Done()

if ctx.CurrentTime()%LengthOfHour == LengthOfHour/2 {
err := p.Client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_OWNER_CHAIRS)
if err != nil {
return WrapCodeError(ErrorCodeFailedToGetOwnerChairs, err)
}

res, err := p.Client.GetOwnerChairs(ctx, &GetOwnerChairsRequest{})
if err != nil {
return WrapCodeError(ErrorCodeFailedToGetOwnerChairs, err)
Expand All @@ -82,6 +88,11 @@ func (p *Owner) Tick(ctx *Context) error {
} else if ctx.CurrentTime()%LengthOfHour == LengthOfHour-1 {
last := lo.MaxBy(p.CompletedRequest.ToSlice(), func(a *Request, b *Request) bool { return a.ServerCompletedAt.After(b.ServerCompletedAt) })
if last != nil {
err := p.Client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_OWNER_SALES)
if err != nil {
return WrapCodeError(ErrorCodeFailedToGetOwnerChairs, err)
}

res, err := p.Client.GetOwnerSales(ctx, &GetOwnerSalesRequest{
Until: last.ServerCompletedAt,
})
Expand Down
22 changes: 21 additions & 1 deletion bench/benchmarker/world/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sync"
"time"

"github.com/isucon/isucon14/bench/benchrun"
"github.com/isucon/isucon14/bench/internal/concurrent"
"github.com/samber/lo"
)
Expand Down Expand Up @@ -104,8 +105,13 @@ func (u *User) Tick(ctx *Context) error {
switch {
// 支払いトークンが未登録
case u.State == UserStatePaymentMethodsNotRegister:
err := u.Client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_CLIENT_REGISTER_3)
if err != nil {
return WrapCodeError(ErrorCodeFailedToRegisterPaymentMethods, err)
}

// トークン登録を試みる
err := u.Client.RegisterPaymentMethods(ctx, u)
err = u.Client.RegisterPaymentMethods(ctx, u)
if err != nil {
return WrapCodeError(ErrorCodeFailedToRegisterPaymentMethods, err)
}
Expand Down Expand Up @@ -155,6 +161,11 @@ func (u *User) Tick(ctx *Context) error {
if !u.Request.Evaluated.Load() {
score := u.Request.CalculateEvaluation().Score()

err := u.Client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_CLIENT_EVALUATION)
if err != nil {
return WrapCodeError(ErrorCodeFailedToEvaluate, err)
}

u.Request.Statuses.Lock()
res, err := u.Client.SendEvaluation(ctx, u.Request, score)
if err != nil {
Expand Down Expand Up @@ -233,6 +244,15 @@ func (u *User) Deactivate() {
}

func (u *User) CheckRequestHistory(ctx *Context) error {
err := u.Client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_CLIENT_CHECK_HISTORY_1)
if err != nil {
return WrapCodeError(ErrorCodeFailedToCheckRequestHistory, err)
}
err = u.Client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_CLIENT_CHECK_HISTORY_2)
if err != nil {
return WrapCodeError(ErrorCodeFailedToCheckRequestHistory, err)
}

res, err := u.Client.GetRequests(ctx)
if err != nil {
return err
Expand Down
23 changes: 22 additions & 1 deletion bench/benchmarker/world/world.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sync/atomic"
"time"

"github.com/isucon/isucon14/bench/benchrun"
"github.com/isucon/isucon14/bench/internal/concurrent"
"github.com/isucon/isucon14/bench/internal/random"
"github.com/samber/lo"
Expand Down Expand Up @@ -179,7 +180,17 @@ func (w *World) CreateUser(ctx *Context, args *CreateUserArgs) (*User, error) {
defer args.Inviter.InvitingLock.Unlock()
}

res, err := w.Client.RegisterUser(ctx, req)
res, err := w.Client.RegisterUser(ctx, req, func(client UserClient) error {
err := client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_CLIENT_REGISTER_1)
if err != nil {
return WrapCodeError(ErrorCodeFailedToRegisterUser, err)
}
err = client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_CLIENT_REGISTER_2)
if err != nil {
return WrapCodeError(ErrorCodeFailedToRegisterUser, err)
}
return nil
})
if err != nil {
return nil, WrapCodeError(ErrorCodeFailedToRegisterUser, err)
}
Expand Down Expand Up @@ -227,6 +238,16 @@ func (w *World) CreateOwner(ctx *Context, args *CreateOwnerArgs) (*Owner, error)

res, err := w.Client.RegisterOwner(ctx, &RegisterOwnerRequest{
Name: registeredData.Name,
}, func(client OwnerClient) error {
err := client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_OWNER_REGISTER_1)
if err != nil {
return WrapCodeError(ErrorCodeFailedToRegisterOwner, err)
}
err = client.BrowserAccess(ctx, benchrun.FRONTEND_PATH_SCENARIO_OWNER_REGISTER_2)
if err != nil {
return WrapCodeError(ErrorCodeFailedToRegisterOwner, err)
}
return nil
})
if err != nil {
return nil, WrapCodeError(ErrorCodeFailedToRegisterOwner, err)
Expand Down
Loading

0 comments on commit d6a7bc1

Please sign in to comment.