37 | エラー ({error.remoteError.code}):{" "} 38 | {error.remoteError.humanMessage}{" "} 39 |
40 |-
43 | {error.remoteError.humanDescriptions.map((v, i) => (
44 |
- {v} 45 | ))} 46 |
├── .editorconfig ├── .envrc ├── .gitattributes ├── .github_ └── workflows │ ├── packer-ci.yml │ ├── packer.yml │ └── webapp.yml ├── .gitignore ├── LICENSE ├── README.md ├── benchmarker ├── .gitignore ├── .go-version ├── Makefile ├── go.mod ├── go.sum ├── main.go ├── model │ ├── benchmark_result.go │ ├── clarification.go │ ├── contest.go │ ├── contestant.go │ ├── host.go │ └── team.go ├── proto │ └── .keep ├── pushserver │ ├── LICENSE │ ├── README.md │ ├── authentication.go │ ├── cmd │ │ ├── .gitignore │ │ ├── Gemfile │ │ ├── Gemfile.lock │ │ ├── README.md │ │ ├── main.go │ │ ├── push.rb │ │ ├── push.sh │ │ └── setup.sh │ ├── ecdh.go │ ├── encryption.go │ ├── message.go │ ├── service.go │ ├── subscription.go │ └── utils.go ├── random │ ├── alphabet.go │ ├── alphabet_test.go │ ├── init.go │ ├── percentage.go │ ├── question.go │ ├── reason.go │ ├── score.go │ ├── score_test.go │ └── team_name.go └── scenario │ ├── action.go │ ├── assert.go │ ├── benchmarker.go │ ├── load.go │ ├── logger.go │ ├── prepare.go │ ├── protobuf.go │ ├── scenario.go │ ├── validation.go │ └── verify.go ├── bin ├── ssh-all.rb └── xsuportal-protoc.sh ├── docs ├── manual.md └── xsucon.md ├── packer ├── .gitignore ├── Makefile ├── base.libsonnet ├── benchmarker.jsonnet ├── ci-run.jsonnet ├── ci.jsonnet ├── contestant.jsonnet ├── files │ ├── 99_disable_netplan.cfg │ ├── itamae │ │ ├── cookbooks │ │ │ ├── apt-source-getenvoy │ │ │ │ ├── default.rb │ │ │ │ └── files │ │ │ │ │ └── etc │ │ │ │ │ ├── 6FF974DB.pem │ │ │ │ │ └── apt │ │ │ │ │ └── sources.list.d │ │ │ │ │ └── getenvoy.list │ │ │ ├── benchmarker │ │ │ │ └── default.rb │ │ │ ├── envoy │ │ │ │ ├── default.rb │ │ │ │ └── templates │ │ │ │ │ └── etc │ │ │ │ │ └── systemd │ │ │ │ │ └── system │ │ │ │ │ └── envoy.service │ │ │ ├── grub │ │ │ │ └── default.rb │ │ │ ├── isucon-user │ │ │ │ └── default.rb │ │ │ ├── isuxportal-supervisor │ │ │ │ ├── default.rb │ │ │ │ ├── files │ │ │ │ │ ├── etc │ │ │ │ │ │ └── systemd │ │ │ │ │ │ │ └── system │ │ │ │ │ │ │ └── isuxportal-supervisor-init.service │ │ │ │ │ └── opt │ │ │ │ │ │ └── isuxportal-supervisor-init │ │ │ │ └── templates │ │ │ │ │ └── etc │ │ │ │ │ └── systemd │ │ │ │ │ └── system │ │ │ │ │ ├── isuxportal-supervisor-init.service │ │ │ │ │ └── isuxportal-supervisor.service │ │ │ ├── langs │ │ │ │ ├── exec.rb │ │ │ │ ├── files │ │ │ │ │ └── home │ │ │ │ │ │ └── isucon │ │ │ │ │ │ ├── .local.env │ │ │ │ │ │ └── .x │ │ │ │ ├── golang.rb │ │ │ │ ├── nodejs.rb │ │ │ │ ├── perl.rb │ │ │ │ ├── php.rb │ │ │ │ ├── ruby.rb │ │ │ │ ├── rust.rb │ │ │ │ ├── verify.rb │ │ │ │ └── versions.rb │ │ │ ├── mysql │ │ │ │ ├── default.rb │ │ │ │ └── files │ │ │ │ │ └── etc │ │ │ │ │ └── mysql │ │ │ │ │ └── mysql.conf.d │ │ │ │ │ └── mysqld.cnf │ │ │ ├── prometheus-node-exporter │ │ │ │ ├── default.rb │ │ │ │ └── files │ │ │ │ │ └── etc │ │ │ │ │ └── systemd │ │ │ │ │ └── system │ │ │ │ │ └── prometheus-node-exporter.service.d │ │ │ │ │ └── dropin.conf │ │ │ ├── protoc │ │ │ │ └── default.rb │ │ │ ├── systemd-timesyncd │ │ │ │ ├── default.rb │ │ │ │ └── files │ │ │ │ │ └── etc │ │ │ │ │ └── systemd │ │ │ │ │ └── timesyncd.conf │ │ │ ├── tls-certificate │ │ │ │ └── default.rb │ │ │ ├── xbuild │ │ │ │ └── default.rb │ │ │ └── xsuportal │ │ │ │ ├── db.rb │ │ │ │ ├── default.rb │ │ │ │ ├── files.rb │ │ │ │ ├── files │ │ │ │ ├── etc │ │ │ │ │ ├── envoy │ │ │ │ │ │ └── config.yaml │ │ │ │ │ └── systemd │ │ │ │ │ │ └── system │ │ │ │ │ │ └── isucon-env-ensure-benchmark-server.service │ │ │ │ └── opt │ │ │ │ │ └── isucon-env-ensure-benchmark-server │ │ │ │ ├── frontend.rb │ │ │ │ ├── golang.rb │ │ │ │ ├── nodejs.rb │ │ │ │ ├── perl.rb │ │ │ │ ├── php.rb │ │ │ │ ├── ruby.rb │ │ │ │ ├── rust.rb │ │ │ │ ├── templates │ │ │ │ ├── etc │ │ │ │ │ └── systemd │ │ │ │ │ │ └── system │ │ │ │ │ │ ├── xsuportal-api-golang.service │ │ │ │ │ │ ├── xsuportal-api-nodejs.service │ │ │ │ │ │ ├── xsuportal-api-ruby.service │ │ │ │ │ │ ├── xsuportal-api-rust.service │ │ │ │ │ │ ├── xsuportal-web-golang.service │ │ │ │ │ │ ├── xsuportal-web-nodejs.service │ │ │ │ │ │ ├── xsuportal-web-ruby.service │ │ │ │ │ │ └── xsuportal-web-rust.service │ │ │ │ └── home │ │ │ │ │ └── isucon │ │ │ │ │ └── env │ │ │ │ ├── tools.rb │ │ │ │ └── web.rb │ │ ├── roles │ │ │ ├── base │ │ │ │ └── default.rb │ │ │ ├── benchmarker │ │ │ │ └── default.rb │ │ │ ├── ci │ │ │ │ ├── default.rb │ │ │ │ └── files │ │ │ │ │ ├── etc │ │ │ │ │ └── systemd │ │ │ │ │ │ └── system │ │ │ │ │ │ ├── benchmarker.slice │ │ │ │ │ │ ├── contestant.slice │ │ │ │ │ │ ├── isucon.slice │ │ │ │ │ │ └── mysql.service.d │ │ │ │ │ │ └── slice.conf │ │ │ │ │ └── home │ │ │ │ │ └── isucon │ │ │ │ │ └── ci.sh │ │ │ ├── contestant │ │ │ │ └── default.rb │ │ │ └── full │ │ │ │ ├── default.rb │ │ │ │ └── files │ │ │ │ └── etc │ │ │ │ └── systemd │ │ │ │ └── system │ │ │ │ ├── benchmarker.slice │ │ │ │ ├── contestant.slice │ │ │ │ ├── isucon.slice │ │ │ │ └── mysql.service.d │ │ │ │ └── slice.conf │ │ ├── site.rb │ │ └── site │ │ │ ├── functions.rb │ │ │ └── mitamae_ext.rb │ ├── sources-ec2.list │ └── sources-generic.list ├── full.jsonnet ├── qemu-http │ ├── meta-data │ └── user-data └── scripts │ ├── latest.rb │ ├── prune.rb │ └── upload.rb ├── proto ├── google │ └── protobuf │ │ └── timestamp.proto └── xsuportal │ ├── error.proto │ ├── resources │ ├── benchmark_job.proto │ ├── benchmark_result.proto │ ├── clarification.proto │ ├── contest.proto │ ├── contestant.proto │ ├── leaderboard.proto │ ├── notification.proto │ ├── staff.proto │ └── team.proto │ └── services │ ├── admin │ ├── benchmark.proto │ ├── clarifications.proto │ ├── dashboard.proto │ ├── initialize.proto │ └── teams.proto │ ├── audience │ ├── dashboard.proto │ └── team_list.proto │ ├── bench │ ├── receiving.proto │ └── reporting.proto │ ├── common │ └── me.proto │ ├── contestant │ ├── benchmark.proto │ ├── clarifications.proto │ ├── dashboard.proto │ ├── login.proto │ ├── logout.proto │ ├── notifications.proto │ └── signup.proto │ └── registration │ ├── create_team.proto │ ├── join.proto │ └── session.proto └── webapp ├── frontend ├── .eslintrc ├── .gitignore ├── index.html ├── javascript │ ├── AdminApp.tsx │ ├── ApiClient.ts │ ├── AudienceApp.tsx │ ├── AudienceDashboard.tsx │ ├── BenchmarkJobDetail.tsx │ ├── BenchmarkJobList.tsx │ ├── BenchmarkJobStatus.tsx │ ├── Clarification.tsx │ ├── ContestClock.tsx │ ├── ContestantApp.tsx │ ├── ErrorMessage.tsx │ ├── Leaderboard.tsx │ ├── Login.tsx │ ├── Logout.tsx │ ├── MaterialIcons.scss │ ├── Navbar.tsx │ ├── NavbarSession.ts │ ├── Registration.tsx │ ├── RegistrationForm.tsx │ ├── RegistrationLogin.tsx │ ├── RegistrationStatus.tsx │ ├── ReloadButton.tsx │ ├── ScoreGraph.tsx │ ├── ScoreGraphColors.ts │ ├── Signup.tsx │ ├── TeamList.tsx │ ├── TeamPins.ts │ ├── TimeDuration.tsx │ ├── Timestamp.tsx │ ├── admin │ │ ├── AdminApiClient.ts │ │ ├── AdminClarificationDetail.tsx │ │ ├── AdminClarificationList.tsx │ │ └── AdminNavbar.tsx │ ├── application.scss │ ├── contestant │ │ ├── ContestantBenchmarkJobDetail.tsx │ │ ├── ContestantBenchmarkJobForm.tsx │ │ ├── ContestantBenchmarkJobList.tsx │ │ ├── ContestantClarificationList.tsx │ │ ├── ContestantDashboard.tsx │ │ ├── ContestantNavbar.tsx │ │ ├── ContestantNotificationSubscriptionPanel.tsx │ │ └── ContestantNotificationsObserver.ts │ ├── font │ │ ├── MaterialIcons-Regular.ttf │ │ └── MaterialIconsOutlined-Regular.otf │ ├── packs │ │ ├── admin.tsx │ │ ├── application.scss │ │ ├── audience.tsx │ │ ├── contestant.tsx │ │ └── navbar.ts │ ├── pb.d.ts │ └── pb.js ├── package.json ├── sw │ ├── src │ │ ├── pb.d.ts │ │ ├── pb.js │ │ └── sw.ts │ └── tsconfig.json ├── tsconfig.json ├── webpack.config.js ├── x41.ico └── yarn.lock ├── generate_vapid_key.sh ├── golang ├── .gitignore ├── Makefile ├── cmd │ ├── benchmark_server │ │ └── main.go │ ├── send_web_push │ │ └── main.go │ └── xsuportal │ │ └── main.go ├── db.go ├── go.mod ├── go.sum ├── notifier.go ├── proto │ ├── google │ │ └── protobuf │ │ │ └── timestamp.pb.go │ └── xsuportal │ │ ├── error.pb.go │ │ ├── resources │ │ ├── benchmark_job.pb.go │ │ ├── benchmark_result.pb.go │ │ ├── clarification.pb.go │ │ ├── contest.pb.go │ │ ├── contestant.pb.go │ │ ├── leaderboard.pb.go │ │ ├── notification.pb.go │ │ ├── staff.pb.go │ │ └── team.pb.go │ │ └── services │ │ ├── admin │ │ ├── benchmark.pb.go │ │ ├── clarifications.pb.go │ │ ├── dashboard.pb.go │ │ ├── initialize.pb.go │ │ └── teams.pb.go │ │ ├── audience │ │ ├── dashboard.pb.go │ │ └── team_list.pb.go │ │ ├── bench │ │ ├── receiving.pb.go │ │ ├── receiving_grpc.pb.go │ │ ├── reporting.pb.go │ │ └── reporting_grpc.pb.go │ │ ├── common │ │ └── me.pb.go │ │ ├── contestant │ │ ├── benchmark.pb.go │ │ ├── clarifications.pb.go │ │ ├── dashboard.pb.go │ │ ├── login.pb.go │ │ ├── logout.pb.go │ │ ├── notifications.pb.go │ │ └── signup.pb.go │ │ └── registration │ │ ├── create_team.pb.go │ │ ├── join.pb.go │ │ └── session.pb.go ├── public ├── types.go └── util │ └── get_env.go ├── nodejs ├── .gitignore ├── package-lock.json ├── package.json ├── proto │ ├── google │ │ └── protobuf │ │ │ ├── timestamp_grpc_pb.js │ │ │ ├── timestamp_pb.d.ts │ │ │ └── timestamp_pb.js │ └── xsuportal │ │ ├── error_grpc_pb.js │ │ ├── error_pb.d.ts │ │ ├── error_pb.js │ │ ├── resources │ │ ├── benchmark_job_grpc_pb.js │ │ ├── benchmark_job_pb.d.ts │ │ ├── benchmark_job_pb.js │ │ ├── benchmark_result_grpc_pb.js │ │ ├── benchmark_result_pb.d.ts │ │ ├── benchmark_result_pb.js │ │ ├── clarification_grpc_pb.js │ │ ├── clarification_pb.d.ts │ │ ├── clarification_pb.js │ │ ├── contest_grpc_pb.js │ │ ├── contest_pb.d.ts │ │ ├── contest_pb.js │ │ ├── contestant_grpc_pb.js │ │ ├── contestant_pb.d.ts │ │ ├── contestant_pb.js │ │ ├── leaderboard_grpc_pb.js │ │ ├── leaderboard_pb.d.ts │ │ ├── leaderboard_pb.js │ │ ├── notification_grpc_pb.js │ │ ├── notification_pb.d.ts │ │ ├── notification_pb.js │ │ ├── staff_grpc_pb.js │ │ ├── staff_pb.d.ts │ │ ├── staff_pb.js │ │ ├── team_grpc_pb.js │ │ ├── team_pb.d.ts │ │ └── team_pb.js │ │ └── services │ │ ├── admin │ │ ├── benchmark_grpc_pb.js │ │ ├── benchmark_pb.d.ts │ │ ├── benchmark_pb.js │ │ ├── clarifications_grpc_pb.js │ │ ├── clarifications_pb.d.ts │ │ ├── clarifications_pb.js │ │ ├── dashboard_grpc_pb.js │ │ ├── dashboard_pb.d.ts │ │ ├── dashboard_pb.js │ │ ├── initialize_grpc_pb.js │ │ ├── initialize_pb.d.ts │ │ ├── initialize_pb.js │ │ ├── teams_grpc_pb.js │ │ ├── teams_pb.d.ts │ │ └── teams_pb.js │ │ ├── audience │ │ ├── dashboard_grpc_pb.js │ │ ├── dashboard_pb.d.ts │ │ ├── dashboard_pb.js │ │ ├── team_list_grpc_pb.js │ │ ├── team_list_pb.d.ts │ │ └── team_list_pb.js │ │ ├── bench │ │ ├── receiving_grpc_pb.d.ts │ │ ├── receiving_grpc_pb.js │ │ ├── receiving_pb.d.ts │ │ ├── receiving_pb.js │ │ ├── reporting_grpc_pb.d.ts │ │ ├── reporting_grpc_pb.js │ │ ├── reporting_pb.d.ts │ │ └── reporting_pb.js │ │ ├── common │ │ ├── me_grpc_pb.js │ │ ├── me_pb.d.ts │ │ └── me_pb.js │ │ ├── contestant │ │ ├── benchmark_grpc_pb.js │ │ ├── benchmark_pb.d.ts │ │ ├── benchmark_pb.js │ │ ├── clarifications_grpc_pb.js │ │ ├── clarifications_pb.d.ts │ │ ├── clarifications_pb.js │ │ ├── dashboard_grpc_pb.js │ │ ├── dashboard_pb.d.ts │ │ ├── dashboard_pb.js │ │ ├── login_grpc_pb.js │ │ ├── login_pb.d.ts │ │ ├── login_pb.js │ │ ├── logout_grpc_pb.js │ │ ├── logout_pb.d.ts │ │ ├── logout_pb.js │ │ ├── notifications_grpc_pb.js │ │ ├── notifications_pb.d.ts │ │ ├── notifications_pb.js │ │ ├── signup_grpc_pb.js │ │ ├── signup_pb.d.ts │ │ └── signup_pb.js │ │ └── registration │ │ ├── create_team_grpc_pb.js │ │ ├── create_team_pb.d.ts │ │ ├── create_team_pb.js │ │ ├── join_grpc_pb.js │ │ ├── join_pb.d.ts │ │ ├── join_pb.js │ │ ├── session_grpc_pb.js │ │ ├── session_pb.d.ts │ │ └── session_pb.js ├── public ├── src │ ├── app.ts │ ├── bin │ │ ├── app.ts │ │ └── benchmark_server │ │ │ └── main.ts │ ├── notifier.ts │ └── sendWebpush.ts └── tsconfig.json ├── ruby ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── app.rb ├── bin │ └── benchmark_server.rb ├── config.ru ├── config │ └── puma.rb ├── grpc │ ├── benchmark_queue_service.rb │ └── benchmark_report_service.rb ├── lib │ ├── database.rb │ ├── google │ │ └── protobuf │ │ │ └── timestamp_pb.rb │ ├── notifier.rb │ ├── routes.rb │ └── xsuportal │ │ ├── error_pb.rb │ │ ├── resources │ │ ├── benchmark_job_pb.rb │ │ ├── benchmark_result_pb.rb │ │ ├── clarification_pb.rb │ │ ├── contest_pb.rb │ │ ├── contestant_pb.rb │ │ ├── leaderboard_pb.rb │ │ ├── notification_pb.rb │ │ ├── staff_pb.rb │ │ └── team_pb.rb │ │ └── services │ │ ├── admin │ │ ├── benchmark_pb.rb │ │ ├── clarifications_pb.rb │ │ ├── dashboard_pb.rb │ │ ├── initialize_pb.rb │ │ └── teams_pb.rb │ │ ├── audience │ │ ├── dashboard_pb.rb │ │ └── team_list_pb.rb │ │ ├── bench │ │ ├── receiving_pb.rb │ │ ├── receiving_services_pb.rb │ │ ├── reporting_pb.rb │ │ └── reporting_services_pb.rb │ │ ├── common │ │ └── me_pb.rb │ │ ├── contestant │ │ ├── benchmark_pb.rb │ │ ├── clarifications_pb.rb │ │ ├── dashboard_pb.rb │ │ ├── login_pb.rb │ │ ├── logout_pb.rb │ │ ├── notifications_pb.rb │ │ └── signup_pb.rb │ │ └── registration │ │ ├── create_team_pb.rb │ │ ├── join_pb.rb │ │ └── session_pb.rb ├── public └── send_web_push.rb ├── rust ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── public └── src │ ├── admin.rs │ ├── audience.rs │ ├── bench.rs │ ├── bin │ ├── benchmark_server.rs │ ├── portal.rs │ └── send_web_push.rs │ ├── common.rs │ ├── contestant.rs │ ├── leaderboard.rs │ ├── lib.rs │ ├── notifier.rs │ ├── proto.rs │ ├── registration.rs │ └── webpush.rs ├── sql ├── schema.sql └── setup.sql └── tools ├── Gemfile ├── Gemfile.lock ├── README.md ├── add_benchmark_job ├── finish_benchmark_job └── show_notifications /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_size = 2 3 | 4 | [*.go] 5 | indent_style = tab 6 | 7 | [Makefile] 8 | indent_style = tab 9 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | export GOPRIVATE="github.com/isucon" 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *_pb.rb linguist-generated=true 2 | 3 | /webapp/golang/proto/**/*.go linguist-generated=true 4 | 5 | /webapp/frontend/javascript/pb.* linguist-generated=true 6 | /webapp/frontend/javascript/pb_admin.* linguist-generated=true 7 | /webapp/frontend/sw/src/pb.* linguist-generated=true 8 | /webapp/frontend/javascript/ScoreGraphColors.tsx linguist-generated=true 9 | 10 | /webapp/nodejs/proto/**/*.js linguist-generated=true 11 | /webapp/nodejs/proto/**/*.ts linguist-generated=true 12 | 13 | /webapp/ruby/debug/**/* export-ignore 14 | /webapp/rust/debug/**/* export-ignore 15 | /webapp/golang/debug/**/* export-ignore 16 | /webapp/nodejs/debug/**/* export-ignore 17 | 18 | /webapp/golang/cmd/debug/**/* export-ignore 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /proto/**/*.go 2 | /benchmarker/proto/**/*.go 3 | /bin/benchmarker 4 | log/ 5 | tmp/ 6 | vapid_private.pem 7 | *.log 8 | /secrets/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ISUCON 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /benchmarker/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | proto/**/*.go 3 | checksum 4 | bin 5 | -------------------------------------------------------------------------------- /benchmarker/.go-version: -------------------------------------------------------------------------------- 1 | 1.15 2 | -------------------------------------------------------------------------------- /benchmarker/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/isucon/isucon10-final/benchmarker 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 // indirect 7 | github.com/golang/protobuf v1.4.2 8 | github.com/gorilla/mux v1.8.0 9 | github.com/hashicorp/golang-lru v0.5.4 10 | github.com/isucon/isucandar v0.0.0-20200930060615-6a85d46588dd 11 | github.com/isucon/isucon10-portal v0.0.0-20201001094636-92a7280580d8 12 | github.com/kr/pretty v0.1.0 // indirect 13 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a 14 | golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect 15 | golang.org/x/text v0.3.3 // indirect 16 | google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 // indirect 17 | google.golang.org/grpc v1.32.0 18 | google.golang.org/protobuf v1.25.0 19 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 20 | gopkg.in/square/go-jose.v2 v2.5.1 21 | ) 22 | -------------------------------------------------------------------------------- /benchmarker/model/clarification.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "time" 7 | 8 | "github.com/isucon/isucon10-final/benchmarker/random" 9 | ) 10 | 11 | var ( 12 | generatedClarCount int64 = 0 13 | disclosePer int64 = 20 14 | ) 15 | 16 | type Clarification struct { 17 | team *Team 18 | id int64 19 | TeamID int64 20 | Question string 21 | Answer string 22 | Disclose bool 23 | answered uint32 24 | smu sync.RWMutex 25 | sentAt time.Time 26 | createdAt time.Time 27 | } 28 | 29 | func NewClarification(team *Team) *Clarification { 30 | count := atomic.AddInt64(&generatedClarCount, 1) 31 | disclose := (count % disclosePer) == 0 32 | 33 | return &Clarification{ 34 | team: team, 35 | id: -1, 36 | TeamID: team.ID, 37 | Question: random.Question(count), 38 | Answer: random.Answer(), 39 | Disclose: disclose, 40 | answered: 0, 41 | smu: sync.RWMutex{}, 42 | sentAt: time.Now().UTC().Add(1 * time.Minute), // とにかく遠くに設定する 43 | createdAt: time.Now().UTC().Add(1 * time.Minute), // とにかく遠くに設定する 44 | } 45 | } 46 | 47 | func (s *Clarification) ID() int64 { 48 | return atomic.LoadInt64(&s.id) 49 | } 50 | 51 | func (s *Clarification) SetID(id int64) { 52 | atomic.StoreInt64(&s.id, id) 53 | } 54 | 55 | func (c *Clarification) IsAnswered() bool { 56 | return atomic.LoadUint32(&c.answered) != 0 57 | } 58 | 59 | func (c *Clarification) Answered() { 60 | atomic.StoreUint32(&c.answered, 1) 61 | } 62 | 63 | func (c *Clarification) CreatedAt() time.Time { 64 | c.smu.RLock() 65 | defer c.smu.RUnlock() 66 | 67 | return c.createdAt 68 | } 69 | 70 | func (c *Clarification) SetCreatedAt(t time.Time) { 71 | c.smu.Lock() 72 | defer c.smu.Unlock() 73 | 74 | c.createdAt = t 75 | } 76 | 77 | func (c *Clarification) SentAt() time.Time { 78 | c.smu.RLock() 79 | defer c.smu.RUnlock() 80 | 81 | return c.sentAt 82 | } 83 | 84 | func (c *Clarification) SetSentAt(t time.Time) { 85 | c.smu.Lock() 86 | defer c.smu.Unlock() 87 | 88 | c.sentAt = t 89 | } 90 | -------------------------------------------------------------------------------- /benchmarker/model/contest.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type Contest struct { 9 | mu sync.RWMutex 10 | RegistrationOpenAt time.Time 11 | ContestStartsAt time.Time 12 | ContestFreezesAt time.Time 13 | ContestEndsAt time.Time 14 | GRPCHost string 15 | GRPCPort int64 16 | Teams []*Team 17 | teamsByID map[int64]*Team 18 | 19 | cmu sync.RWMutex 20 | clarifications []*Clarification 21 | } 22 | 23 | func NewContest(now time.Time, testMode bool) *Contest { 24 | now = now.UTC().Truncate(time.Second).Add(1 * time.Second) 25 | 26 | c := &Contest{ 27 | mu: sync.RWMutex{}, 28 | RegistrationOpenAt: now, 29 | ContestStartsAt: now.Add(10 * time.Second), 30 | ContestFreezesAt: now.Add(50 * time.Second), 31 | ContestEndsAt: now.Add(60 * time.Second), 32 | GRPCHost: "", 33 | GRPCPort: 0, 34 | Teams: []*Team{}, 35 | teamsByID: map[int64]*Team{}, 36 | 37 | cmu: sync.RWMutex{}, 38 | clarifications: []*Clarification{}, 39 | } 40 | 41 | if testMode { 42 | c.RegistrationOpenAt = now.Add(2 * time.Second) 43 | c.ContestStartsAt = now.Add(4 * time.Second) 44 | c.ContestFreezesAt = now.Add(6 * time.Second) 45 | c.ContestEndsAt = now.Add(8 * time.Second) 46 | } 47 | 48 | return c 49 | } 50 | 51 | func (c *Contest) AddTeam(team *Team) { 52 | c.mu.Lock() 53 | defer c.mu.Unlock() 54 | 55 | c.Teams = append(c.Teams, team) 56 | c.teamsByID[team.ID] = team 57 | } 58 | 59 | func (c *Contest) GetTeam(id int64) *Team { 60 | c.mu.RLock() 61 | defer c.mu.RUnlock() 62 | 63 | return c.teamsByID[id] 64 | } 65 | 66 | func (c *Contest) AddClar(clar *Clarification) { 67 | c.cmu.Lock() 68 | defer c.cmu.Unlock() 69 | 70 | c.clarifications = append(c.clarifications, clar) 71 | } 72 | 73 | func (c *Contest) Clarifications() []*Clarification { 74 | c.cmu.RLock() 75 | defer c.cmu.RUnlock() 76 | 77 | clars := make([]*Clarification, len(c.clarifications)) 78 | copy(clars, c.clarifications[:]) 79 | 80 | return clars 81 | } 82 | -------------------------------------------------------------------------------- /benchmarker/model/contestant.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | 7 | "github.com/isucon/isucandar/agent" 8 | "github.com/isucon/isucon10-final/benchmarker/random" 9 | ) 10 | 11 | const ( 12 | CONTESTANT_ID_LENGTH = 12 13 | CONTESTANT_PASSWORD_LENGTH = 64 14 | ) 15 | 16 | type Contestant struct { 17 | ID string 18 | Password string 19 | Name string 20 | IsStudent bool 21 | latestNotificationID int64 22 | 23 | Agent *agent.Agent 24 | 25 | rmu sync.RWMutex 26 | receivedClarIDs []int64 27 | } 28 | 29 | func NewContestant() (*Contestant, error) { 30 | a, err := agent.NewAgent() 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | id := random.Alphabet(CONTESTANT_ID_LENGTH) 36 | 37 | return &Contestant{ 38 | ID: id, 39 | Password: id, // random.Alphabet(CONTESTANT_PASSWORD_LENGTH), 40 | Name: random.Alphabet(20), 41 | IsStudent: random.Pecentage(1, 10), 42 | latestNotificationID: 0, 43 | Agent: a, 44 | rmu: sync.RWMutex{}, 45 | receivedClarIDs: []int64{}, 46 | }, nil 47 | } 48 | 49 | func NewAdmin() (*Contestant, error) { 50 | admin, err := NewContestant() 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | admin.ID = "admin" 56 | admin.Password = "admin" 57 | return admin, nil 58 | } 59 | 60 | func (c *Contestant) LatestNotificationID() int64 { 61 | return atomic.LoadInt64(&c.latestNotificationID) 62 | } 63 | 64 | func (c *Contestant) UpdateLatestNotificationID(id int64) { 65 | if c.LatestNotificationID() < id { 66 | atomic.StoreInt64(&c.latestNotificationID, id) 67 | } 68 | } 69 | 70 | func (c *Contestant) ReceiveClarID(id int64) { 71 | c.rmu.Lock() 72 | defer c.rmu.Unlock() 73 | 74 | c.receivedClarIDs = append(c.receivedClarIDs, id) 75 | } 76 | 77 | func (c *Contestant) ReceivedClarIDs() []int64 { 78 | c.rmu.RLock() 79 | defer c.rmu.RUnlock() 80 | 81 | return c.receivedClarIDs 82 | } 83 | -------------------------------------------------------------------------------- /benchmarker/model/host.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Host struct { 4 | Name string 5 | } 6 | -------------------------------------------------------------------------------- /benchmarker/proto/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon10-final/9b43f02b6248610eaf786fa9f25396437dd1ffca/benchmarker/proto/.keep -------------------------------------------------------------------------------- /benchmarker/pushserver/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Sorah Fukumori 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /benchmarker/pushserver/README.md: -------------------------------------------------------------------------------- 1 | # RFC8030 Web Push, RFC8292 VAPID, RFC8291 Web Push Encryption: Mock Server Implementation in Go 2 | 3 | Subscription Receipt is not implemented. 4 | 5 | ## License 6 | 7 | MIT License 8 | -------------------------------------------------------------------------------- /benchmarker/pushserver/cmd/.gitignore: -------------------------------------------------------------------------------- 1 | vapid_* 2 | sub_*.json 3 | https_*.pem 4 | certs/ 5 | -------------------------------------------------------------------------------- /benchmarker/pushserver/cmd/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'webpush' 3 | -------------------------------------------------------------------------------- /benchmarker/pushserver/cmd/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | hkdf (0.3.0) 5 | jwt (2.2.2) 6 | webpush (1.0.0) 7 | hkdf (~> 0.2) 8 | jwt (~> 2.0) 9 | 10 | PLATFORMS 11 | ruby 12 | 13 | DEPENDENCIES 14 | webpush 15 | 16 | BUNDLED WITH 17 | 2.1.4 18 | -------------------------------------------------------------------------------- /benchmarker/pushserver/cmd/README.md: -------------------------------------------------------------------------------- 1 | # pushserver test 2 | 3 | ``` 4 | ./setup.sh 5 | 6 | go run . & 7 | ./push.sh sub_1.json & 8 | ./push.sh sub_2.json & 9 | ./push.sh sub_3.json & 10 | ``` 11 | -------------------------------------------------------------------------------- /benchmarker/pushserver/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | 10 | "github.com/isucon/isucon10-final/benchmarker/pushserver" 11 | ) 12 | 13 | type subscriber struct { 14 | Endpoint string `json:"endpoint"` 15 | P256DH string `json:"p256dh"` 16 | Auth string `json:"auth"` 17 | } 18 | 19 | func main() { 20 | flag.Parse() 21 | vapidPublicKey, err := ioutil.ReadFile("./vapid_public.txt") 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | service := pushserver.NewService("https://localhost:11001", 10) 27 | service.OnInvalidPush = onInvalidPush 28 | 29 | for i := 0; i < 5; i++ { 30 | // these flags MUST be true. 31 | s, err := service.Subscribe(&pushserver.SubscriptionOption{Vapid: string(vapidPublicKey[:])}, true, true) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | sr := &subscriber{ 37 | Endpoint: s.GetURL(), 38 | P256DH: s.GetP256DH(), 39 | Auth: s.GetAuth(), 40 | } 41 | srJSON, err := json.Marshal(sr) 42 | if err != nil { 43 | panic(err) 44 | } 45 | ioutil.WriteFile(fmt.Sprintf("./sub_%d.json", i), srJSON, 0644) 46 | 47 | go receivePush(i, s) 48 | } 49 | 50 | http.ListenAndServeTLS("localhost:11001", "./https_crt.pem", "./https_key.pem", service.HTTP()) 51 | } 52 | 53 | func receivePush(i int, sub *pushserver.Subscription) { 54 | for { 55 | msg := <-sub.GetChannel() 56 | if msg == nil { 57 | break 58 | } 59 | fmt.Printf("%d/%s: id=%v, ttl=%v, topic=%v, unencrypted=%v, body=%v\n", i, sub.ID, msg.ID, msg.TTL, msg.Topic, msg.WasUnencrypted(), string(msg.Body[:])) 60 | } 61 | } 62 | 63 | func onInvalidPush(id string, err error) { 64 | fmt.Printf("%s: err %+v\n", id, err) 65 | } 66 | -------------------------------------------------------------------------------- /benchmarker/pushserver/cmd/push.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'webpush' 3 | 4 | sub = JSON.parse(File.read(ARGV[0])) 5 | sub2 = JSON.parse(File.read(ARGV[1])) if ARGV[1] 6 | 7 | vapid = Webpush::VapidKey.from_pem(File.read('./vapid_private.pem')) 8 | vapid2 = Webpush::VapidKey.from_pem(File.read('./vapid_private2.pem')) 9 | 10 | def test(m, &block) 11 | puts "#{ARGV[0]} #{m}" 12 | block.call(m) 13 | rescue Webpush::Error => e 14 | warn e.inspect 15 | end 16 | 17 | loop do 18 | test("valid: #{Time.now.to_s}") do |m| 19 | puts "#{ARGV[0]} #{m}" 20 | Webpush.payload_send( 21 | message: m, 22 | endpoint: sub.fetch('endpoint'), 23 | p256dh: sub.fetch('p256dh'), 24 | auth: sub.fetch('auth'), 25 | vapid: vapid.to_h.merge(subject: 'test@example.com'), 26 | ) 27 | end 28 | 29 | test("wrong vapid: #{Time.now.to_s}") do |m| 30 | Webpush.payload_send( 31 | message: m, 32 | endpoint: sub.fetch('endpoint'), 33 | p256dh: sub.fetch('p256dh'), 34 | auth: sub.fetch('auth'), 35 | vapid: vapid2.to_h.merge(subject: 'test@example.com'), 36 | ) 37 | end 38 | 39 | test("wrong enc: #{Time.now.to_s}") do |m| 40 | Webpush.payload_send( 41 | message: m, 42 | endpoint: sub.fetch('endpoint'), 43 | p256dh: sub2.fetch('p256dh'), 44 | auth: sub2.fetch('auth'), 45 | vapid: vapid.to_h.merge(subject: 'test@example.com'), 46 | ) 47 | end if sub2 48 | 49 | sleep 1 50 | end 51 | -------------------------------------------------------------------------------- /benchmarker/pushserver/cmd/push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export SSL_CERT_DIR=$(pwd)/certs 3 | exec ruby push.rb "$@" 4 | -------------------------------------------------------------------------------- /benchmarker/pushserver/cmd/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | cd $(dirname $0) 3 | 4 | bundle install 5 | 6 | openssl ecparam -name prime256v1 -genkey -out vapid_private.pem 7 | ruby -ropenssl -e "puts [OpenSSL::PKey::EC.new(ARGF.read,'').public_key.to_bn.to_s(2)].pack('m0').tr('+/','-_').gsub(/=/,'')" vapid_private.pem > vapid_public.txt 8 | 9 | openssl ecparam -name prime256v1 -genkey -out vapid_private2.pem 10 | ruby -ropenssl -e "puts [OpenSSL::PKey::EC.new(ARGF.read,'').public_key.to_bn.to_s(2)].pack('m0').tr('+/','-_').gsub(/=/,'')" vapid_private2.pem > vapid_public2.txt 11 | 12 | openssl req -x509 -nodes -days 3650 -newkey ec:<(openssl ecparam -name prime256v1) -keyout https_key.pem -out https_crt.pem -subj '/CN=localhost/' 13 | 14 | mkdir -p certs/ 15 | cp https_crt.pem certs/ 16 | openssl rehash certs || /usr/local/opt/openssl/bin/openssl rehash certs 17 | -------------------------------------------------------------------------------- /benchmarker/pushserver/ecdh.go: -------------------------------------------------------------------------------- 1 | package pushserver 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "fmt" 8 | "math/big" 9 | ) 10 | 11 | type ecPublicKey struct { 12 | x *big.Int 13 | y *big.Int 14 | } 15 | 16 | type ecKey struct { 17 | publicKey ecPublicKey 18 | privateKey []byte 19 | } 20 | 21 | func (pub *ecPublicKey) intoEcdsaPublicKey() *ecdsa.PublicKey { 22 | return &ecdsa.PublicKey{ 23 | Curve: elliptic.P256(), 24 | X: pub.x, 25 | Y: pub.y, 26 | } 27 | } 28 | 29 | func (pub *ecPublicKey) Encode() string { 30 | return encodeBase64(elliptic.Marshal(elliptic.P256(), pub.x, pub.y)) 31 | } 32 | 33 | // SEC1: https://www.secg.org/sec1-v2.pdf 34 | // Section 6.1. Elliptic Curve Diffie-Hellman Scheme 35 | func (p *ecKey) ecdhSecret(publicKey *ecPublicKey, short bool) []byte { 36 | curve := elliptic.P256() 37 | x, _ := curve.ScalarMult(publicKey.x, publicKey.y, p.privateKey) 38 | 39 | if short { 40 | // Workaround for invalid clients https://github.com/SherClockHolmes/webpush-go/blob/af9d240f5def12dc7c23a73999092c9d937be7a5/webpush.go#L105 41 | return x.Bytes() 42 | } 43 | 44 | // SEC1: 6.1.3. Key Agreement Operation 45 | // Action 2. Convert $$ z $$ to an octet string the conversion routine specified in Section 2.3.5. 46 | // Section 2.3.5. Field-Element-to-Octet-String Conversion 47 | // > where $$ mlen = log_2q/8 $$ 48 | // Section 2.3.7. Integer-to-Octet-String Conversion 49 | mlen := curve.Params().BitSize / 8 50 | buf := make([]byte, mlen) 51 | x.FillBytes(buf) 52 | return buf 53 | } 54 | 55 | func newEcKey() (*ecKey, error) { 56 | privateKey, x, y, err := elliptic.GenerateKey(elliptic.P256(), rand.Reader) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return &ecKey{privateKey: privateKey, publicKey: ecPublicKey{x, y}}, nil 61 | } 62 | 63 | func unmarshalEcPublicKey(bytes []byte) (*ecPublicKey, error) { 64 | x, y := elliptic.Unmarshal(elliptic.P256(), bytes) 65 | if x == nil { 66 | return nil, fmt.Errorf("Cannot unmarshal given P-256 encoded public key") 67 | } 68 | 69 | return &ecPublicKey{x, y}, nil 70 | } 71 | 72 | func unmarshalEncodedEcPublicKey(key string) (*ecPublicKey, error) { 73 | bytes, err := decodeBase64(key) 74 | if err != nil { 75 | return nil, fmt.Errorf("Cannot decode given P-256 encoded public key: %w", err) 76 | } 77 | 78 | return unmarshalEcPublicKey(bytes) 79 | } 80 | -------------------------------------------------------------------------------- /benchmarker/pushserver/message.go: -------------------------------------------------------------------------------- 1 | package pushserver 2 | 3 | import "fmt" 4 | 5 | // RawMessage is a raw push message (might be encrypted) given at HTTP Push Request. 6 | type RawMessage struct { 7 | Body []byte 8 | TTL int64 9 | Topic string 10 | 11 | Encoding string 12 | } 13 | 14 | const aes128gcm = "aes128gcm" 15 | 16 | func (rm *RawMessage) isEncrypted() bool { 17 | return rm.Encoding == aes128gcm 18 | } 19 | 20 | // Message is a message 21 | type Message struct { 22 | ID string 23 | Body []byte 24 | TTL int64 25 | Topic string 26 | 27 | audience string 28 | wasEncrypted bool 29 | } 30 | 31 | func newMessageForSubscription(sub *Subscription, rawMessage *RawMessage) (*Message, error) { 32 | isEncrypted := rawMessage.isEncrypted() 33 | 34 | body := rawMessage.Body 35 | if isEncrypted { 36 | d := &decryption{ 37 | data: body, 38 | privateKey: sub.privateKey, 39 | secret: sub.sharedSecret, 40 | } 41 | plaintext, err := d.decrypt() 42 | if err != nil { 43 | return nil, err 44 | } 45 | body = plaintext 46 | } 47 | 48 | msg := &Message{ 49 | ID: sub.newMessageID(), 50 | Body: body, 51 | TTL: rawMessage.TTL, 52 | Topic: rawMessage.Topic, 53 | wasEncrypted: isEncrypted, 54 | audience: sub.Audience, 55 | } 56 | return msg, nil 57 | } 58 | 59 | // WasUnencrypted returns true when a message was not pushed with a encryption (RFC8291) and has a content. 60 | func (m *Message) WasUnencrypted() bool { 61 | return !m.wasEncrypted && len(m.Body) > 0 62 | } 63 | 64 | // GetPath returns a HTTP path for this message 65 | func (m *Message) GetPath() string { 66 | return fmt.Sprintf(`/message/%s`, m.ID) 67 | } 68 | 69 | // GetURL returns a HTTP URL for this message 70 | func (m *Message) GetURL() string { 71 | return fmt.Sprintf(`%s/push/%s`, m.audience, m.ID) 72 | } 73 | -------------------------------------------------------------------------------- /benchmarker/pushserver/utils.go: -------------------------------------------------------------------------------- 1 | package pushserver 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "fmt" 8 | "io" 9 | "sync" 10 | ) 11 | 12 | func decodeBase64(key string) ([]byte, error) { 13 | bytes, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(key) 14 | if err == nil { 15 | return bytes, nil 16 | } 17 | 18 | return base64.StdEncoding.WithPadding(base64.NoPadding).DecodeString(key) 19 | } 20 | 21 | func encodeBase64(key []byte) string { 22 | return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(key) 23 | } 24 | 25 | var idSalt []byte 26 | var initonce sync.Once 27 | 28 | func boot() { 29 | generateIDSalt() 30 | } 31 | 32 | func generateIDSalt() { 33 | newSalt := make([]byte, 16) 34 | n, err := io.ReadFull(rand.Reader, newSalt) 35 | if err != nil || n != len(newSalt) { 36 | panic("cannot generate id salt") 37 | } 38 | idSalt = newSalt 39 | } 40 | 41 | func saltedIDHash(model string, a int64, b int64) string { 42 | initonce.Do(boot) 43 | hash := sha256.Sum256([]byte(fmt.Sprintf("%s\t%s\t%d\t%d", idSalt, model, a, b))) 44 | return encodeBase64(hash[:]) 45 | } 46 | -------------------------------------------------------------------------------- /benchmarker/random/alphabet.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 8 | 9 | func Alphabet(length int) string { 10 | b := make([]rune, length) 11 | for i := range b { 12 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 13 | } 14 | return string(b) 15 | } 16 | -------------------------------------------------------------------------------- /benchmarker/random/alphabet_test.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAlphabet(t *testing.T) { 8 | a := Alphabet(10) 9 | if len(a) != 10 { 10 | t.Fatal(a) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /benchmarker/random/init.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func init() { 9 | rand.Seed(time.Now().UnixNano()) 10 | } 11 | -------------------------------------------------------------------------------- /benchmarker/random/percentage.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | func Pecentage(decimal int, parameter int) bool { 8 | return rand.Intn(parameter) < decimal 9 | } 10 | -------------------------------------------------------------------------------- /benchmarker/random/question.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/binary" 6 | "fmt" 7 | "math/rand" 8 | ) 9 | 10 | var ( 11 | singleQuestionPrefix = []string{ 12 | "SSH が", 13 | "ブラウザで", 14 | "アプリケーションが", 15 | "サーバーが", 16 | "システムが", 17 | } 18 | singleQuestionSuffix = []string{ 19 | "接続できません", 20 | "うまく動きません", 21 | "壊れています", 22 | "どう動いているかわかりません", 23 | } 24 | ) 25 | 26 | var ( 27 | salt = []byte{} 28 | ) 29 | 30 | func init() { 31 | saltBytes := make([]byte, 128) 32 | _, err := rand.Read(saltBytes) 33 | if err != nil { 34 | panic(err) 35 | } 36 | salt = saltBytes[:] 37 | } 38 | 39 | func Question(id int64) string { 40 | h := sha256.New() 41 | if err := binary.Write(h, binary.LittleEndian, id); err != nil { 42 | panic(err) 43 | } 44 | if _, err := h.Write(salt); err != nil { 45 | panic(err) 46 | } 47 | body := singleQuestionPrefix[rand.Intn(len(singleQuestionPrefix))] + singleQuestionSuffix[rand.Intn(len(singleQuestionSuffix))] 48 | return fmt.Sprintf("%s\n%x", body, h.Sum(nil)) 49 | } 50 | 51 | var ( 52 | singleAnswerPrefix = []string{ 53 | "返答", 54 | "解答", 55 | "問題の再現", 56 | "運営内での協議", 57 | } 58 | singleAnswerSuffix = []string{ 59 | "しました", 60 | "できかねます", 61 | "は後ほど個別にお送りします", 62 | } 63 | ) 64 | 65 | func Answer() string { 66 | return singleAnswerPrefix[rand.Intn(len(singleAnswerPrefix))] + singleAnswerSuffix[rand.Intn(len(singleAnswerSuffix))] 67 | } 68 | -------------------------------------------------------------------------------- /benchmarker/random/reason.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | successReasons = []string{ 10 | "load: verification: 検証に成功しました", 11 | "load: http: ステータスコードの検証成功", 12 | "load: html: HTML 構造の検証に成功しました", 13 | "load: checksum: MD5 チェックサムの一致", 14 | } 15 | errorReasons = []string{ 16 | "load: verification: 検証に失敗しました", 17 | "load: timeout: タイムアウトしました", 18 | "load: cancel: キャンセルされました", 19 | "load: error: エラーを検知しました", 20 | "load: http: ステータスコードの検証失敗", 21 | "load: html: HTML 構造の検証に失敗しました", 22 | "load: checksum: MD5 チェックサムの不一致", 23 | } 24 | reasons []string = []string{} 25 | ) 26 | 27 | func init() { 28 | reasons = append(reasons, successReasons...) 29 | reasons = append(reasons, errorReasons...) 30 | } 31 | 32 | const ( 33 | MAX_REASON_SIZE = 200 34 | ) 35 | 36 | func Reason(passed bool) string { 37 | r := reasons 38 | if passed { 39 | r = successReasons 40 | } 41 | 42 | c := 0 43 | lines := []string{} 44 | for { 45 | text := r[rand.Intn(len(r))] 46 | if c+len(text)+1 < MAX_REASON_SIZE { 47 | lines = append(lines, text) 48 | c += len(text) + 1 49 | } else { 50 | break 51 | } 52 | } 53 | return strings.Join(lines, "\n") 54 | } 55 | -------------------------------------------------------------------------------- /benchmarker/random/score_test.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestScoreBaseScore(t *testing.T) { 8 | s := NewScoreGenerator() 9 | 10 | for i := 0; i < 100; i++ { 11 | t.Logf("%d", s.Generate(int64(i)).Int()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /benchmarker/scenario/assert.go: -------------------------------------------------------------------------------- 1 | package scenario 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type equal interface { 8 | Eqaul(interface{}) bool 9 | } 10 | 11 | func AssertEqual(msg string, expected interface{}, actual interface{}) bool { 12 | r := assertEqual(expected, actual) 13 | if !r { 14 | AdminLogger.Printf("%s: expected: %v / actual: %v", msg, expected, actual) 15 | } 16 | return r 17 | } 18 | 19 | func assertEqual(expected interface{}, actual interface{}) bool { 20 | if expected == nil || actual == nil { 21 | return expected == actual 22 | } 23 | 24 | if e, ok := expected.(equal); ok { 25 | return e.Eqaul(actual) 26 | } 27 | 28 | actualType := reflect.TypeOf(actual) 29 | if actualType == nil { 30 | return false 31 | } 32 | expectedValue := reflect.ValueOf(expected) 33 | if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { 34 | return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) 35 | } 36 | 37 | return false 38 | } 39 | -------------------------------------------------------------------------------- /benchmarker/scenario/logger.go: -------------------------------------------------------------------------------- 1 | package scenario 2 | 3 | import ( 4 | "os" 5 | 6 | "log" 7 | ) 8 | 9 | var ( 10 | ContestantLogger *log.Logger 11 | AdminLogger *log.Logger 12 | ) 13 | 14 | func init() { 15 | ContestantLogger = log.New(os.Stdout, "", log.Lmicroseconds) 16 | AdminLogger = log.New(os.Stderr, "", log.Lmicroseconds) 17 | } 18 | -------------------------------------------------------------------------------- /benchmarker/scenario/scenario.go: -------------------------------------------------------------------------------- 1 | package scenario 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/isucon/isucandar/agent" 8 | "github.com/isucon/isucandar/failure" 9 | "github.com/isucon/isucandar/pubsub" 10 | "github.com/isucon/isucon10-final/benchmarker/model" 11 | "github.com/isucon/isucon10-final/benchmarker/pushserver" 12 | ) 13 | 14 | var ( 15 | ErrScenarioCretical failure.StringCode = "scenario-critical" 16 | ErrScenarioCancel failure.StringCode = "scenario-cancel" 17 | ) 18 | 19 | type Scenario struct { 20 | mu sync.RWMutex 21 | BaseURL string 22 | UseTLS bool 23 | PushService *pushserver.Service 24 | Language string 25 | Contest *model.Contest 26 | TeamCapacity int32 27 | NoLoad bool 28 | NoClar bool 29 | 30 | bpubsub *pubsub.PubSub 31 | rpubsub *pubsub.PubSub 32 | firstMarkedAt time.Time 33 | markedAt time.Time 34 | } 35 | 36 | func NewScenario() (*Scenario, error) { 37 | return &Scenario{ 38 | mu: sync.RWMutex{}, 39 | TeamCapacity: -1, 40 | NoLoad: false, 41 | bpubsub: pubsub.NewPubSub(), 42 | rpubsub: pubsub.NewPubSub(), 43 | firstMarkedAt: time.Now().Add(24 * time.Hour), 44 | markedAt: time.Now(), 45 | }, nil 46 | } 47 | 48 | func (s *Scenario) NewAgent(opts ...agent.AgentOption) (*agent.Agent, error) { 49 | opts = append(opts, agent.WithBaseURL(s.BaseURL)) 50 | return agent.NewAgent(opts...) 51 | } 52 | 53 | func (s *Scenario) AddBenchmarker(teamID int64) { 54 | s.bpubsub.Publish(teamID) 55 | } 56 | 57 | func (s *Scenario) AddAudience(count int) { 58 | for i := 0; i < count; i++ { 59 | s.rpubsub.Publish(true) 60 | } 61 | } 62 | 63 | func (s *Scenario) LatestMarkedAt() time.Time { 64 | s.mu.RLock() 65 | defer s.mu.RUnlock() 66 | 67 | return s.markedAt 68 | } 69 | 70 | func (s *Scenario) Mark(t time.Time) { 71 | s.mu.Lock() 72 | defer s.mu.Unlock() 73 | 74 | t = t.Truncate(time.Microsecond) 75 | if s.firstMarkedAt.After(t) { 76 | s.firstMarkedAt = t 77 | } 78 | if t.After(s.markedAt) { 79 | s.markedAt = t 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /bin/ssh-all.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'thread' 3 | 4 | SUBNETS = %w( 5 | 10.160.1. 6 | 10.160.10. 7 | 10.160.15. 8 | 10.160.20. 9 | 10.160.24. 10 | 10.160.27. 11 | 10.160.86. 12 | 10.161.47. 13 | 10.161.59. 14 | 10.161.77. 15 | 10.161.94. 16 | 10.162.10. 17 | 10.162.13. 18 | 10.162.23. 19 | 10.162.28. 20 | 10.162.29. 21 | 10.162.47. 22 | 10.162.76. 23 | 10.162.86. 24 | 10.163.4. 25 | 10.163.31. 26 | 10.163.47. 27 | 10.163.53. 28 | 10.163.73. 29 | 10.163.75. 30 | 10.163.89. 31 | 10.163.94. 32 | 10.164.6. 33 | 10.164.15. 34 | 10.164.28. 35 | 10.164.40. 36 | 10.164.72. 37 | 10.164.73. 38 | 10.164.74. 39 | 10.164.83. 40 | 10.164.98. 41 | 10.165.15. 42 | 10.165.34. 43 | 10.165.35. 44 | 10.165.36. 45 | 10.165.37. 46 | 10.165.38. 47 | ) 48 | 49 | mode = ARGV.shift 50 | hosts = case mode 51 | when 'bench' 52 | SUBNETS.map{ |_| "#{_}104" } 53 | when 'contestant' 54 | SUBNETS.flat_map{ |_| ["#{_}101", "#{_}102", "#{_}103"] } 55 | when '101', '102', '103' 56 | SUBNETS.map{ |_| "#{_}#{mode}" } 57 | else 58 | raise "mode should be: bench, contestant, 101, 102, 103" 59 | end 60 | 61 | scp = false 62 | if ARGV[0] == '--scp' 63 | scp = true 64 | ARGV.shift 65 | raise if ARGV.size < 2 66 | end 67 | ssh = !scp 68 | 69 | puts "SSH to **#{mode}**, #{ARGV.inspect}" 70 | puts "Hosts:" 71 | puts hosts.map { |_| " * #{_}" } 72 | puts 73 | puts 74 | sleep 3 75 | 76 | queue = Queue.new 77 | hosts.each do |h| 78 | queue.push(h) 79 | end 80 | queue.close 81 | 82 | i = 0 83 | 84 | CONCURRENCY = 10 85 | ths = CONCURRENCY.times.map do 86 | Thread.new do 87 | failed = [] 88 | while host = queue.pop 89 | i += 1 90 | puts "===> #{host} [#{i+1}/#{hosts.size}, remaining=#{queue.size}]" 91 | retval = if ssh 92 | system("ssh", "-o", "ConnectTimeout=6", host, *ARGV) 93 | else 94 | system("scp", "-o", "ConnectTimeout=6", *ARGV[0...-2], ARGV[-2], "#{host}:#{ARGV[-1]}") 95 | end 96 | failed << host unless retval 97 | end 98 | failed 99 | end 100 | end 101 | 102 | faileds = ths.flat_map(&:value) 103 | 104 | unless faileds.empty? 105 | puts 106 | puts "Failures:" 107 | puts faileds 108 | exit 1 109 | end 110 | -------------------------------------------------------------------------------- /bin/xsuportal-protoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh -xe 2 | cd "$(dirname $0)/.." 3 | BASE_DIR=$(pwd) 4 | 5 | cd ${BASE_DIR}/webapp/frontend 6 | rm -rf javascript/pb*.js javascript/pb*.d.ts || true 7 | npx pbjs -p ../../proto -t static-module -w commonjs -o javascript/pb.js ../../proto/**/*.proto 8 | npx pbts -o javascript/pb.d.ts javascript/pb.js 9 | 10 | npx pbjs -p ../../proto -t static-module -w commonjs -o sw/src/pb.js \ 11 | ../../proto/google/**/*.proto \ 12 | ../../proto/xsuportal/resources/notification.proto 13 | npx pbts -o sw/src/pb.d.ts sw/src/pb.js 14 | 15 | cd ${BASE_DIR}/webapp/ruby 16 | rm -rf lib/**/*_pb.rb || true 17 | bundle exec grpc_tools_ruby_protoc -I../../proto --ruby_out=./lib --grpc_out=./lib ../../proto/**/*.proto 18 | 19 | cd ${BASE_DIR}/webapp/golang 20 | rm -rf ./proto || true 21 | mkdir -p ./proto 22 | protoc --go_out=./proto --go-grpc_out=./proto --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative -I ../../proto ../../proto/**/*.proto 23 | 24 | cd ${BASE_DIR}/webapp/nodejs 25 | rm -rf ./src/proto || true 26 | mkdir -p ./src/proto 27 | npm run gen -------------------------------------------------------------------------------- /packer/.gitignore: -------------------------------------------------------------------------------- 1 | files-generated/ 2 | files-cached/ 3 | tmp/ 4 | target/ 5 | output/ 6 | packer_cache/ 7 | files/tls-*.pem 8 | -------------------------------------------------------------------------------- /packer/benchmarker.jsonnet: -------------------------------------------------------------------------------- 1 | local base = import './base.libsonnet'; 2 | 3 | base { 4 | arg_variant: 'benchmarker', 5 | } 6 | -------------------------------------------------------------------------------- /packer/ci-run.jsonnet: -------------------------------------------------------------------------------- 1 | local base = import './base.libsonnet'; 2 | 3 | base { 4 | arg_variant: 'ci-run', 5 | variables+: { 6 | source_img: 'foobar', 7 | source_checksum: '01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b', // dummy 8 | lang: 'ruby', 9 | }, 10 | builder_qemu+:: { 11 | iso_checksum: '{{user "source_checksum"}}', 12 | iso_url: '{{user "source_img"}}', 13 | use_backing_file: true, 14 | skip_compaction: true, 15 | }, 16 | download_log_provisioner:: { 17 | type: 'file', 18 | direction: 'download', 19 | source: '/home/isucon/ci.log', 20 | destination: 'output/ci.log', 21 | }, 22 | provisioners: [ 23 | $.common_provisioners.wait_cloud_init, 24 | { 25 | type: 'shell', 26 | inline: [ 27 | 'set +e', 28 | 'sudo -u isucon -H /home/isucon/ci.sh {{user "lang"}}', 29 | 'echo $? | sudo -u isucon tee -a /home/isucon/ci.log', 30 | ], 31 | }, 32 | $.download_log_provisioner, 33 | ], 34 | "error-cleanup-provisioner": $.download_log_provisioner, 35 | } 36 | -------------------------------------------------------------------------------- /packer/ci.jsonnet: -------------------------------------------------------------------------------- 1 | local base = import './base.libsonnet'; 2 | 3 | base { 4 | arg_variant: 'ci', 5 | variables+: { 6 | full_source_img: 'foobar', 7 | full_source_checksum: '01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b', // dummy 8 | }, 9 | builder_ec2+:: { 10 | source_ami_filter: { 11 | filters: { 12 | 'virtualization-type': 'hvm', 13 | 'root-device-type': 'ebs', 14 | name: 'isucon10f-' + $.arg_arch + '-contestant-*', 15 | }, 16 | owners: ['516315029474'], 17 | most_recent: true, 18 | }, 19 | }, 20 | builder_qemu+:: { 21 | iso_checksum: '{{user "full_source_checksum"}}', 22 | iso_url: '{{user "full_source_img"}}', 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /packer/contestant.jsonnet: -------------------------------------------------------------------------------- 1 | local base = import './base.libsonnet'; 2 | 3 | base { 4 | arg_variant: 'contestant', 5 | provisioners_plus:: [ 6 | $.common_provisioners.generate_cache { 7 | only: ['qemu'], 8 | }, 9 | $.common_provisioners.download_cache { 10 | only: ['qemu'], 11 | }, 12 | ], 13 | } 14 | -------------------------------------------------------------------------------- /packer/files/99_disable_netplan.cfg: -------------------------------------------------------------------------------- 1 | network: {config: disabled} 2 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/apt-source-getenvoy/default.rb: -------------------------------------------------------------------------------- 1 | remote_file '/etc/6FF974DB.pem' do 2 | owner 'root' 3 | group 'root' 4 | mode '0644' 5 | 6 | notifies :run, 'execute[apt-key add /etc/6FF974DB.pem]', :immediately 7 | end 8 | 9 | execute 'apt-key add /etc/6FF974DB.pem' do 10 | action :nothing 11 | end 12 | 13 | remote_file '/etc/apt/sources.list.d/getenvoy.list' do 14 | owner 'root' 15 | group 'root' 16 | mode '0644' 17 | notifies :run, 'execute[apt-get update]' 18 | end 19 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/apt-source-getenvoy/files/etc/apt/sources.list.d/getenvoy.list: -------------------------------------------------------------------------------- 1 | deb [arch=amd64] https://dl.bintray.com/tetrate/getenvoy-deb focal stable 2 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/benchmarker/default.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'tls-certificate' 2 | include_cookbook 'xsuportal::files' 3 | include_cookbook 'xsuportal::frontend' 4 | include_cookbook 'langs::golang' 5 | include_cookbook 'protoc' 6 | 7 | execute "rm -rf ~isucon/benchmarker; tar xf /dev/shm/files-generated/archive.tar -C ~isucon/ benchmarker" do 8 | user "isucon" 9 | not_if "test -e ~isucon/benchmarker" 10 | end 11 | 12 | execute "rm -rf ~isucon/benchmarker/vendor; tar xf /dev/shm/files-generated/benchmarker-vendor.tar -C ~isucon/benchmarker" do 13 | user "isucon" 14 | not_if "test -e ~isucon/isucandar/vendor" 15 | end 16 | 17 | execute "cd ~isucon/benchmarker && PATH=/home/isucon/local/golang/bin:${PATH} make" do 18 | user 'isucon' 19 | not_if "test -e ~isucon/bin/benchmarker" 20 | end 21 | 22 | # include_cookbook 'isuxportal-supervisor' 23 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/envoy/default.rb: -------------------------------------------------------------------------------- 1 | node.reverse_merge!( 2 | envoy: { 3 | slice: nil, 4 | }, 5 | ) 6 | 7 | include_cookbook 'apt-source-getenvoy' 8 | 9 | package 'getenvoy-envoy' 10 | 11 | directory '/etc/envoy' do 12 | owner 'root' 13 | group 'root' 14 | mode '0755' 15 | end 16 | 17 | template '/etc/systemd/system/envoy.service' do 18 | owner 'root' 19 | group 'root' 20 | mode '0644' 21 | notifies :run, 'execute[systemctl daemon-reload]' 22 | end 23 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/envoy/templates/etc/systemd/system/envoy.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Envoy Proxy 3 | After=network.target 4 | 5 | [Service] 6 | User=isucon 7 | ExecStart=/usr/bin/envoy -c /etc/envoy/config.yaml --concurrency 1 8 | RuntimeDirectory=envoy 9 | LogsDirectory=envoy 10 | AmbientCapabilities=CAP_NET_BIND_SERVICE 11 | CapabilityBoundingSet=CAP_NET_BIND_SERVICE 12 | 13 | <%- if node[:envoy][:slice] -%> 14 | Slice=<%= node[:envoy][:slice] %> 15 | <%- end -%> 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | 20 | # vim: ft=systemd 21 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/grub/default.rb: -------------------------------------------------------------------------------- 1 | node.reverse_merge!( 2 | cmdline: { 3 | maxcpus: nil, 4 | mem: nil, 5 | }, 6 | ) 7 | 8 | execute 'update-grub' do 9 | action :nothing 10 | end 11 | 12 | file "/etc/default/grub" do 13 | action :edit 14 | 15 | block do |content| 16 | content.gsub!(/^GRUB_CMDLINE_LINUX=.*(?:maxcpus|mem)=.*$/, '') 17 | content.gsub!(/\n*\z/m, ?\n) 18 | content << %(GRUB_CMDLINE_LINUX="${GRUB_CMDLINE_LINUX} maxcpus=#{node[:cmdline][:maxcpus]}"\n) if node[:cmdline][:maxcpus] 19 | content << %(GRUB_CMDLINE_LINUX="${GRUB_CMDLINE_LINUX} mem=#{node[:cmdline][:mem]}"\n) if node[:cmdline][:mem] 20 | content 21 | end 22 | 23 | notifies :run, 'execute[update-grub]' 24 | end 25 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/isucon-user/default.rb: -------------------------------------------------------------------------------- 1 | username = 'isucon' 2 | 3 | group username do 4 | gid 1100 5 | end 6 | 7 | user username do 8 | uid 1100 9 | gid 1100 10 | home "/home/#{username}" 11 | create_home true 12 | shell "/bin/bash" 13 | end 14 | 15 | directory "/home/#{username}" do 16 | owner username 17 | group username 18 | mode "755" 19 | end 20 | 21 | directory "/home/#{username}/.ssh" do 22 | owner username 23 | group username 24 | mode "0700" 25 | end 26 | 27 | directory '/home/isucon/local' do 28 | owner 'isucon' 29 | group 'isucon' 30 | mode '0755' 31 | end 32 | 33 | directory '/home/isucon/bin' do 34 | owner 'isucon' 35 | group 'isucon' 36 | mode '0755' 37 | end 38 | 39 | 40 | file "/home/#{username}/.ssh/authorized_keys" do 41 | content "\n" 42 | owner username 43 | group username 44 | mode "600" 45 | end 46 | 47 | file '/etc/sudoers.d/isucon' do 48 | content "#{username} ALL=(ALL) NOPASSWD:ALL\n" 49 | owner 'root' 50 | group 'root' 51 | mode '440' 52 | end 53 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/isuxportal-supervisor/default.rb: -------------------------------------------------------------------------------- 1 | node.reverse_merge!( 2 | benchmarker: { 3 | }, 4 | ) 5 | include_cookbook 'langs::ruby' 6 | 7 | execute "install -o isucon -g isucon -m 0755 /dev/shm/files-generated/isuxportal-supervisor /home/isucon/bin/isuxportal-supervisor" 8 | 9 | remote_file "/opt/isuxportal-supervisor-init" do 10 | owner 'root' 11 | group 'root' 12 | mode '0755' 13 | notifies :run, 'execute[systemctl daemon-reload]' 14 | end 15 | 16 | remote_file "/etc/systemd/system/isuxportal-supervisor-init.service" do 17 | owner 'root' 18 | group 'root' 19 | mode '0644' 20 | notifies :run, 'execute[systemctl daemon-reload]' 21 | end 22 | 23 | template "/etc/systemd/system/isuxportal-supervisor.service" do 24 | owner 'root' 25 | group 'root' 26 | mode '0644' 27 | notifies :run, 'execute[systemctl daemon-reload]' 28 | end 29 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/isuxportal-supervisor/files/etc/systemd/system/isuxportal-supervisor-init.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isuxportal-supervisor-init 3 | After=network.target 4 | 5 | [Service] 6 | Type=oneshot 7 | RemainAfterExit=yes 8 | ExecStart=/home/isucon/.x ruby /opt/isuxportal-supervisor-init 9 | TimeoutStartSec=10s 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | 14 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/isuxportal-supervisor/files/opt/isuxportal-supervisor-init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'open-uri' 3 | 4 | ENDPOINT_METADATA_URL = 'http://169.254.169.254/s/endpoint/398c4de5b9e71ef42b7dca9e4d0d1b661ae85c4996f8d73fb015e3ae6b08a978222784bf348baf6c8e7c96a6d1a2886d' 5 | TOKEN_METADATA_URL = 'http://169.254.169.254/s/token/398c4de5b9e71ef42b7dca9e4d0d1b661ae85c4996f8d73fb015e3ae6b08a978222784bf348baf6c8e7c96a6d1a2886d' 6 | TEAM_ID_METADATA_URL = 'http://169.254.169.254/teamid' 7 | 8 | team_id = URI.open(TEAM_ID_METADATA_URL, 'r', &:read).chomp 9 | token = URI.open(TOKEN_METADATA_URL, 'r', &:read).chomp 10 | endpoint = URI.open(ENDPOINT_METADATA_URL, 'r', &:read).chomp 11 | 12 | content = <<-EOF 13 | ISUXPORTAL_SUPERVISOR_ENDPOINT_URL=#{endpoint} 14 | ISUXPORTAL_SUPERVISOR_TOKEN=#{token} 15 | ISUXPORTAL_SUPERVISOR_TEAM_ID=#{team_id} 16 | 17 | EOF 18 | 19 | File.write "/run/isuxportal-supervisor.env", content 20 | File.write "/etc/isuxportal-supervisor.env-backup", content 21 | 22 | system "systemctl", "daemon-reload" # to reload %H 23 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/isuxportal-supervisor/templates/etc/systemd/system/isuxportal-supervisor-init.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isuxportal-supervisor-init 3 | After=network.target 4 | 5 | [Service] 6 | Type=oneshot 7 | RemainAfterExit=yes 8 | ExecStart=/home/isucon/.x ruby /opt/isuxportal-supervisor-init 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | 13 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/isuxportal-supervisor/templates/etc/systemd/system/isuxportal-supervisor.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isuxportal-supervisor 3 | Wants=isuxportal-supervisor-init.service 4 | After=isuxportal-supervisor-init.service prometheus-node-exporter.service network.target 5 | StartLimitIntervalSec=0 6 | 7 | [Service] 8 | User=isucon 9 | ExecStartPre=-/bin/mv /home/isucon/bin/isuxportal-supervisor.new /home/isucon/bin/isuxportal-supervisor 10 | ExecStartPre=-/bin/mv /home/isucon/benchmarker/bin/benchmarker.new /home/isucon/benchmarker/bin/benchmarker 11 | ExecStart=/home/isucon/bin/isuxportal-supervisor /home/isucon/benchmarker/bin/benchmarker \ 12 | -tls \ 13 | -host-advertise isubench.t.isucon.dev \ 14 | -push-service-port 443 \ 15 | -tls-cert /etc/ssl/private/tls-cert.pem \ 16 | -tls-key /etc/ssl/private/tls-key.pem \ 17 | -prom-out /run/prometheus-node-exporter/textfile/xsuconbench.prom 18 | WorkingDirectory=/home/isucon/benchmarker 19 | LogsDirectory=isuxportal-supervisor 20 | 21 | AmbientCapabilities=CAP_NET_BIND_SERVICE 22 | CapabilityBoundingSet=CAP_NET_BIND_SERVICE 23 | LimitNOFILE=2000000 24 | 25 | #Environment=ISUXPORTAL_SUPERVISOR_ENDPOINT_URL=https://portal-grpc-dev.x.isucon.dev 26 | #Environment=ISUXPORTAL_SUPERVISOR_ENDPOINT_URL=https://portal-grpc-prd.x.isucon.dev 27 | #Environment=ISUXPORTAL_SUPERVISOR_TOKEN=himitsu 28 | #Environment=ISUXPORTAL_SUPERVISOR_TEAM_ID=12345 29 | Environment=ISUXPORTAL_SUPERVISOR_INSTANCE_NAME=%H 30 | Environment=ISUXPORTAL_SUPERVISOR_HARD_TIMEOUT=180 31 | Environment=ISUXPORTAL_SUPERVISOR_LOG_DIRECTORY=/var/log/isuxportal-supervisor 32 | Environment=ISUXPORTAL_SUPERVISOR_INTERVAL_AFTER_EMPTY_RECEIVE=2 33 | 34 | EnvironmentFile=/run/isuxportal-supervisor.env 35 | 36 | RestartSec=2s 37 | Restart=on-failure 38 | 39 | TimeoutStopSec=200s 40 | KillMode=mixed 41 | 42 | <%- if node.dig(:benchmarker, :slice) -%> 43 | Slice=<%= node.dig(:benchmarker, :slice) %> 44 | <%- end -%> 45 | 46 | [Install] 47 | WantedBy=multi-user.target 48 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/exec.rb: -------------------------------------------------------------------------------- 1 | remote_file '/home/isucon/.local.env' do 2 | owner 'isucon' 3 | group 'isucon' 4 | mode '0644' 5 | end 6 | 7 | remote_file '/home/isucon/.x' do 8 | owner 'isucon' 9 | group 'isucon' 10 | mode '0755' 11 | end 12 | 13 | execute 'echo "source /home/isucon/.local.env" >> /home/isucon/.bashrc' do 14 | user 'isucon' 15 | not_if 'grep -q "isucon/.local.env" /home/isucon/.bashrc' 16 | end 17 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/files/home/isucon/.local.env: -------------------------------------------------------------------------------- 1 | export PATH=/home/isucon/local/golang/bin:$PATH 2 | export PATH=/home/isucon/local/nodejs/bin:$PATH 3 | export PATH=/home/isucon/local/perl/bin:$PATH 4 | export PATH=/home/isucon/local/php/bin:$PATH 5 | export PATH=/home/isucon/local/ruby/bin:$PATH 6 | 7 | #yarn 8 | export PATH="/home/isucon/.yarn/bin:/home/isucon/.config/yarn/global/node_modules/.bin:$PATH" 9 | 10 | #rust 11 | export CARGO_HOME=/home/isucon/.cargo 12 | export RUSTUP_HOME=/home/isucon/.rustup 13 | . /home/isucon/.cargo/env 14 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/files/home/isucon/.x: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | . /home/isucon/.local.env 3 | exec "$@" 4 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/golang.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::versions' 2 | include_cookbook 'xbuild' 3 | 4 | version = node[:langs][:versions].fetch(:golang) 5 | execute "rm -rf /home/isucon/local/golang; /opt/xbuild/go-install #{version} /home/isucon/local/golang" do 6 | user 'isucon' 7 | not_if "/home/isucon/local/golang/bin/go version | grep -q 'go#{version} '" 8 | end 9 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/nodejs.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'xbuild' 2 | 3 | version = node[:langs][:versions].fetch(:nodejs) 4 | 5 | execute "rm -rf /home/isucon/local/nodejs; /opt/xbuild/node-install #{version} /home/isucon/local/nodejs" do 6 | user 'isucon' 7 | not_if "/home/isucon/local/nodejs/bin/node --version | grep -q '^#{version}$'" 8 | end 9 | 10 | execute 'curl -o- -L https://yarnpkg.com/install.sh | /home/isucon/.x bash' do 11 | user 'isucon' 12 | not_if "test -e /home/isucon/.yarn" 13 | end 14 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/perl.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::versions' 2 | include_cookbook 'xbuild' 3 | 4 | version = node[:langs][:versions].fetch(:perl) 5 | 6 | execute "rm -rf /home/isucon/local/perl; /opt/xbuild/perl-install #{version} /home/isucon/local/perl -- -Duselongdouble -j $(nproc)" do 7 | user 'isucon' 8 | not_if "/home/isucon/local/perl/bin/perl --version |grep -q '(v#{version})'" 9 | end 10 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/php.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::versions' 2 | include_cookbook 'xbuild' 3 | 4 | version = node[:langs][:versions].fetch(:php) 5 | 6 | execute "rm -rf /home/isucon/local/php; /opt/xbuild/php-install #{version} /home/isucon/local/php -- ..." do 7 | command "/opt/xbuild/php-install #{version} /home/isucon/local/php -- " \ 8 | ' --enable-bcmath' \ 9 | ' --enable-calendar' \ 10 | ' --enable-cli' \ 11 | ' --enable-fpm' \ 12 | ' --enable-mbregex' \ 13 | ' --enable-mbstring' \ 14 | ' --enable-opcache' \ 15 | ' --enable-pcntl' \ 16 | ' --enable-pdo' \ 17 | ' --enable-shmop' \ 18 | ' --enable-sockets' \ 19 | ' --enable-sysvmsg' \ 20 | ' --enable-sysvsem' \ 21 | ' --enable-sysvshm' \ 22 | ' --with-bz2' \ 23 | ' --with-curl' \ 24 | ' --with-mysqli=mysqlnd' \ 25 | ' --with-openssl' \ 26 | ' --with-pdo-mysql=mysqlnd' \ 27 | ' --with-zlib' 28 | 29 | user 'isucon' 30 | not_if "/home/isucon/local/php/bin/php --version | grep -q 'PHP #{version} '" 31 | end 32 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/ruby.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::versions' 2 | include_cookbook 'xbuild' 3 | 4 | version = node[:langs][:versions].fetch(:ruby) 5 | 6 | execute "rm -rf /home/isucon/local/ruby; /opt/xbuild/ruby-install #{version} /home/isucon/local/ruby" do 7 | user 'isucon' 8 | not_if "/home/isucon/local/ruby/bin/ruby -v | grep '^ruby #{version}p'" 9 | end 10 | 11 | execute "/home/isucon/.x gem install bundler -v '~> 2.1.4' --no-doc" do 12 | user 'isucon' 13 | not_if "/home/isucon/local/ruby/bin/bundle version | grep -q 'version 2.1'" 14 | end 15 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/rust.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::versions' 2 | version = node[:langs][:versions].fetch(:rust) 3 | 4 | execute "curl --proto '=https' --tlsv1.2 -LSsf -o /opt/rustup-init.sh https://sh.rustup.rs" do 5 | not_if 'test -e /opt/rustup-init.sh' 6 | end 7 | 8 | # FIXME: cache but difficult as it creates binaries in ~/.cargo/bin (while keeping CARGO_HOME as is) 9 | execute "rm -rf ~isucon/.cargo ~isucon/.rustup; sh /opt/rustup-init.sh -y --no-modify-path --profile default --default-toolchain #{version} -c rustfmt" do 10 | user 'isucon' 11 | not_if "/home/isucon/.x cargo version && ( /home/isucon/.x rustc --version | grep -q '#{version}' )" 12 | end 13 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/verify.rb: -------------------------------------------------------------------------------- 1 | execute "verify ~isucon/local binary dyld" do 2 | command "( find ~isucon/local/ -name '*.so' -print0; find ~isucon/local/ -executable -print0 ) | xargs -0 -P $(nproc) -n1 bash -c \"" \ 3 | " if echo $1 | grep -q '/local/rust/'; then continue; fi;" \ 4 | " if ldd $1 2>&1 | grep 'not found'; then" \ 5 | " echo $1; exit 1;" \ 6 | " fi\"" 7 | 8 | user 'isucon' 9 | end 10 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/langs/versions.rb: -------------------------------------------------------------------------------- 1 | # This file is used for build caching. Improper cache key will reduce build efficiency (cache will not be updated while xbuild re-run). 2 | # cache-epoch: 2 3 | 4 | node.reverse_merge!( 5 | langs: { 6 | versions: { 7 | golang: '1.15.2', 8 | nodejs: 'v14.13.0', # 'v' prefix is important. 9 | # perl: '5.32.0', 10 | # php: '7.4.10', 11 | ruby: '2.7.1', 12 | rust: '1.46.0', 13 | }, 14 | }, 15 | ) 16 | 17 | execute "tar xpf /var/tmp/files-cached/local.tar.gz -C ~isucon/local --xattrs" do 18 | only_if 'test -e /var/tmp/files-cached/local.tar.gz' 19 | end 20 | 21 | file "/home/isucon/local/.versions" do 22 | content "#{node[:langs][:versions].to_a.map { |k,v| [k,v] }.sort_by(&:first).to_json}\n" 23 | owner 'isucon' 24 | group 'isucon' 25 | mode '0644' 26 | notifies :run, 'execute[rm -f /home/isucon/local/.cache]' 27 | end 28 | 29 | execute 'rm -f /home/isucon/local/.cache' do 30 | action :nothing 31 | end 32 | 33 | include_cookbook 'langs::verify' 34 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/mysql/default.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | mysql-server-8.0 3 | mysql-server-core-8.0 4 | mysql-client-8.0 5 | mysql-client-core-8.0 6 | 7 | libmysqlclient-dev 8 | 9 | mysql-client 10 | mysql-common 11 | mysql-server 12 | ).each do |_| 13 | package _ 14 | end 15 | 16 | remote_file "/etc/mysql/mysql.conf.d/mysqld.cnf" do 17 | owner 'root' 18 | group 'root' 19 | mode '0644' 20 | notifies :restart, 'service[mysql.service]' 21 | end 22 | 23 | service "mysql.service" do 24 | action [:enable, :start] 25 | end 26 | 27 | execute %|mysql -uroot -e "create user isucon@localhost identified with mysql_native_password by 'isucon'; grant all privileges on *.* to isucon@localhost; flush privileges;"| do 28 | user 'root' 29 | not_if %(mysql -uroot -e "select User,Host from mysql.user"|grep -q isucon) 30 | end 31 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/prometheus-node-exporter/default.rb: -------------------------------------------------------------------------------- 1 | package 'prometheus-node-exporter' 2 | 3 | directory '/etc/systemd/system/prometheus-node-exporter.service.d' do 4 | owner 'root' 5 | group 'root' 6 | mode '0755' 7 | end 8 | 9 | remote_file '/etc/systemd/system/prometheus-node-exporter.service.d/dropin.conf' do 10 | owner 'root' 11 | group 'root' 12 | mode '0644' 13 | notifies :run, 'execute[systemctl daemon-reload]' 14 | end 15 | 16 | service 'prometheus-node-exporter.service' do 17 | action :enable 18 | end 19 | 20 | 21 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/prometheus-node-exporter/files/etc/systemd/system/prometheus-node-exporter.service.d/dropin.conf: -------------------------------------------------------------------------------- 1 | # vim: ft=systemd 2 | [Service] 3 | RuntimeDirectory=prometheus-node-exporter 4 | RuntimeDirectoryPreserve=yes 5 | ExecStartPre=/bin/mkdir -p /run/prometheus-node-exporter/textfile 6 | ExecStartPre=/bin/chmod 2777 /run/prometheus-node-exporter/textfile 7 | ExecStart= 8 | ExecStart=/usr/bin/prometheus-node-exporter \ 9 | --collector.bonding \ 10 | --collector.conntrack \ 11 | --collector.cpu \ 12 | --collector.cpufreq \ 13 | --collector.diskstats \ 14 | --collector.entropy \ 15 | --collector.filefd \ 16 | --collector.filesystem \ 17 | --collector.hwmon \ 18 | --collector.loadavg \ 19 | --collector.logind \ 20 | --collector.mdadm \ 21 | --collector.meminfo \ 22 | --collector.netclass \ 23 | --collector.netdev \ 24 | --collector.netstat \ 25 | --collector.nfs \ 26 | --collector.nfsd \ 27 | --collector.sockstat \ 28 | --collector.stat \ 29 | --collector.systemd \ 30 | --collector.textfile \ 31 | --collector.time \ 32 | --collector.uname \ 33 | --collector.vmstat \ 34 | --collector.zfs \ 35 | --collector.textfile.directory /run/prometheus-node-exporter/textfile \ 36 | --collector.systemd.unit-whitelist "sshd?.service|isuxportal-supervisor.service" 37 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/protoc/default.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::golang' 2 | 3 | execute 'install protoc' do 4 | command 'curl -Lo /dev/shm/protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v3.13.0/protoc-3.13.0-linux-x86_64.zip && ( cd /dev/shm && echo "22a179b8c785b531841ee3c5fb710e6e06dc55fa45a2bd896c34552bd6068051f7f812de1f478583015912b54dd4f347 protoc.zip" | sha384sum -c --strict ) && ( mkdir -p /tmp/protoc && cd /tmp/protoc && unzip /dev/shm/protoc.zip && install -Dm 0755 bin/protoc /usr/local/bin/protoc && find include/ -type f -exec install -Dm 644 "{}" "/usr/local/{}" ";" )' 5 | not_if 'test -e /usr/local/bin/protoc' 6 | end 7 | 8 | execute 'GOBIN=/root/go/bin /home/isucon/.x go get google.golang.org/protobuf/cmd/protoc-gen-go && mv /root/go/bin/protoc-gen-go /usr/local/bin/protoc-gen-go' do 9 | not_if 'test -e /usr/local/bin/protoc-gen-go' 10 | end 11 | 12 | execute 'GOBIN=/root/go/bin /home/isucon/.x go get google.golang.org/grpc/cmd/protoc-gen-go-grpc && mv /root/go/bin/protoc-gen-go-grpc /usr/local/bin/protoc-gen-go-grpc' do 13 | not_if 'test -e /usr/local/bin/protoc-gen-go-grpc' 14 | end 15 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/systemd-timesyncd/default.rb: -------------------------------------------------------------------------------- 1 | remote_file '/etc/systemd/timesyncd.conf' do 2 | owner 'root' 3 | group 'root' 4 | mode '0644' 5 | notifies :restart, 'service[systemd-timesyncd.service]' 6 | end 7 | 8 | service 'systemd-timesyncd.service' do 9 | action [:start, :enable] 10 | end 11 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/systemd-timesyncd/files/etc/systemd/timesyncd.conf: -------------------------------------------------------------------------------- 1 | [Time] 2 | NTP=time1.google.com time2.google.com time3.google.com time4.google.com 3 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/tls-certificate/default.rb: -------------------------------------------------------------------------------- 1 | directory '/etc/ssl/private' do 2 | owner 'root' 3 | group 'root' 4 | mode '0755' 5 | end 6 | 7 | execute 'install -m644 -o isucon -g isucon /dev/shm/files/tls-cert.pem /etc/ssl/private/tls-cert.pem' do 8 | not_if 'test -e /etc/ssl/private/tls-cert.pem' 9 | end 10 | 11 | execute 'install -m600 -o isucon -g isucon /dev/shm/files/tls-key.pem /etc/ssl/private/tls-key.pem' do 12 | not_if 'test -e /etc/ssl/private/tls-key.pem' 13 | end 14 | 15 | 16 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xbuild/default.rb: -------------------------------------------------------------------------------- 1 | directory "/opt/xbuild" do 2 | owner 'isucon' 3 | group 'isucon' 4 | mode '0755' 5 | end 6 | 7 | execute "git clone --depth 1 https://github.com/tagomoris/xbuild /opt/xbuild" do 8 | user 'isucon' 9 | not_if "test -e /opt/xbuild/.git" 10 | end 11 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/db.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'mysql' 2 | 3 | execute %|mysql -uroot -e 'create database if not exists `xsuportal` default character set utf8mb4;'| do 4 | user 'root' 5 | end 6 | 7 | execute %|MYSQL_PWD=isucon mysql -uisucon xsuportal < ~isucon/webapp/sql/schema.sql| do 8 | user 'isucon' 9 | end 10 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/default.rb: -------------------------------------------------------------------------------- 1 | node.reverse_merge!( 2 | xsuportal: { 3 | slice: nil, 4 | }, 5 | ) 6 | 7 | if node[:xsuportal][:ignore_failed_build] 8 | node[:xsuportal][:ignore_failed_build] = proc { |l| ">/home/isucon/build.#{l}.log 2>&1 || touch /home/isucon/builderror-#{l}" } 9 | else 10 | node[:xsuportal][:ignore_failed_build] = proc { } 11 | end 12 | 13 | include_cookbook 'xsuportal::files' 14 | 15 | include_cookbook 'xsuportal::db' 16 | 17 | include_cookbook 'protoc' 18 | include_cookbook 'xsuportal::tools' 19 | 20 | include_cookbook 'xsuportal::golang' 21 | include_cookbook 'xsuportal::nodejs' 22 | # include_cookbook 'xsuportal::perl' 23 | # include_cookbook 'xsuportal::php' 24 | include_cookbook 'xsuportal::ruby' 25 | include_cookbook 'xsuportal::rust' 26 | 27 | include_cookbook 'xsuportal::frontend' 28 | 29 | include_cookbook 'xsuportal::web' 30 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/files.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::ruby' 2 | 3 | execute "rm -rf ~isucon/webapp; tar xf /dev/shm/files-generated/archive.tar -C ~isucon/ webapp" do 4 | user "isucon" 5 | not_if "test -e ~isucon/webapp" 6 | end 7 | 8 | execute "rm -rf ~isucon/proto; tar xf /dev/shm/files-generated/archive.tar -C ~isucon/ proto" do 9 | user "isucon" 10 | not_if "test -e ~isucon/proto" 11 | end 12 | 13 | template "/home/isucon/env" do 14 | owner 'isucon' 15 | group 'root' 16 | mode '0640' 17 | end 18 | 19 | remote_file "/opt/isucon-env-ensure-benchmark-server" do 20 | owner 'root' 21 | owner 'root' 22 | mode '0755' 23 | end 24 | 25 | remote_file "/etc/systemd/system/isucon-env-ensure-benchmark-server.service" do 26 | owner 'root' 27 | owner 'root' 28 | mode '0644' 29 | notifies :run, 'execute[systemctl daemon-reload]', :immediately 30 | end 31 | 32 | service 'isucon-env-ensure-benchmark-server.service' do 33 | action [:enable] 34 | end 35 | 36 | 37 | if node.dig(:xsuportal, :ci_cache) 38 | execute "cd /opt/ci-cache && rm /opt/ci-cache/.do && for x in *; do mkdir -p ~isucon/webapp/${x}; mv -v ${x}/* -t ~isucon/webapp/${x}/; done" do 39 | only_if 'test -e /opt/ci-cache/.do' 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/files/etc/systemd/system/isucon-env-ensure-benchmark-server.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=set environment variable for xsuportal 3 | After=cloud-init.service network.target network-online.target 4 | Before=isuxportal-web-ruby.service isuxportal-web-rust.service isuxportal-web-php.service isuxportal-web-perl.service isuxportal-web-nodejs.service isuxportal-web-golang.service 5 | 6 | [Service] 7 | Type=oneshot 8 | RemainAfterExit=yes 9 | User=isucon 10 | ExecStart=/home/isucon/.x /opt/isucon-env-ensure-benchmark-server 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/files/opt/isucon-env-ensure-benchmark-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | if File.read('/home/isucon/env').match?(/^BENCHMARK_SERVER_HOST/) 3 | exit 4 | end 5 | 6 | ipv4_address = IO.popen(%w(ip r get 8.8.8.8), 'r', &:read).match(/src\s+(.+?)\s/)[1] 7 | 8 | isuname = nil 9 | File.open('/etc/hosts', 'r') do |io| 10 | io.each do |l| 11 | m = l.match(/^#{Regexp.escape(ipv4_address)} (.+\.t\.isucon\.dev)/) 12 | if m 13 | isuname = m[1] 14 | end 15 | end 16 | end 17 | 18 | if isuname 19 | File.open('/home/isucon/env', 'a') do |io| 20 | io.puts 21 | io.puts "BENCHMARK_SERVER_HOST=#{isuname}" 22 | io.puts "BENCHMARK_SERVER_PORT=443" 23 | end 24 | puts "BENCHMARK_SERVER_HOST set to #{isuname.inspect}" 25 | end 26 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/frontend.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::nodejs' 2 | 3 | execute 'cd ~isucon/webapp/frontend && /home/isucon/.x yarn' do 4 | user 'isucon' 5 | end 6 | 7 | execute 'cd ~isucon/webapp/frontend && rm -rf public; NODE_ENV=production /home/isucon/.x npx webpack --mode production' do 8 | user 'isucon' 9 | not_if 'test -e ~isucon/webapp/frontend/public' 10 | end 11 | 12 | execute 'cd ~isucon/webapp/frontend && cp x41.ico public/favicon.ico' do 13 | user 'isucon' 14 | not_if 'test -e ~isucon/webapp/frontend/public/favicon.ico' 15 | end 16 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/golang.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::golang' 2 | 3 | execute "cd ~isucon/webapp/golang && /home/isucon/.x make #{node[:xsuportal][:ignore_failed_build]['golang']}" do 4 | user 'isucon' 5 | end 6 | 7 | template '/etc/systemd/system/xsuportal-web-golang.service' do 8 | owner 'root' 9 | group 'root' 10 | mode '0644' 11 | notifies :run, 'execute[systemctl daemon-reload]' 12 | end 13 | 14 | template '/etc/systemd/system/xsuportal-api-golang.service' do 15 | owner 'root' 16 | group 'root' 17 | mode '0644' 18 | notifies :run, 'execute[systemctl daemon-reload]' 19 | end 20 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/nodejs.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::nodejs' 2 | 3 | execute "cd ~isucon/webapp/nodejs && /home/isucon/.x npm ci #{node[:xsuportal][:ignore_failed_build]['nodejs']}" do 4 | user 'isucon' 5 | end 6 | 7 | execute "cd ~isucon/webapp/nodejs && /home/isucon/.x npm run build #{node[:xsuportal][:ignore_failed_build]['nodejs']}" do 8 | user 'isucon' 9 | end 10 | 11 | template '/etc/systemd/system/xsuportal-web-nodejs.service' do 12 | owner 'root' 13 | group 'root' 14 | mode '0644' 15 | notifies :run, 'execute[systemctl daemon-reload]' 16 | end 17 | 18 | template '/etc/systemd/system/xsuportal-api-nodejs.service' do 19 | owner 'root' 20 | group 'root' 21 | mode '0644' 22 | notifies :run, 'execute[systemctl daemon-reload]' 23 | end 24 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/perl.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::perl' 2 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/php.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::php' 2 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/ruby.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::ruby' 2 | 3 | execute "( cd ~isucon/webapp/ruby && /home/isucon/.x bundle config set deployment true && /home/isucon/.x bundle config set path vendor/bundle && /home/isucon/.x bundle install --jobs 20 ) #{node[:xsuportal][:ignore_failed_build]['ruby']}" do 4 | user 'isucon' 5 | not_if 'cd ~isucon/webapp/ruby && test -e .bundle && /home/isucon/.x bundle check' 6 | end 7 | 8 | execute 'cd ~isucon/webapp/ruby && /home/isucon/.x bundle config set deployment "false"' do 9 | user 'isucon' 10 | end 11 | 12 | template '/etc/systemd/system/xsuportal-web-ruby.service' do 13 | owner 'root' 14 | group 'root' 15 | mode '0644' 16 | notifies :run, 'execute[systemctl daemon-reload]' 17 | end 18 | 19 | template '/etc/systemd/system/xsuportal-api-ruby.service' do 20 | owner 'root' 21 | group 'root' 22 | mode '0644' 23 | notifies :run, 'execute[systemctl daemon-reload]' 24 | end 25 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/rust.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::rust' 2 | 3 | execute "cd ~isucon/webapp/rust && /home/isucon/.x cargo build --release --locked #{node[:xsuportal][:ignore_failed_build]['rust']}" do 4 | user 'isucon' 5 | end 6 | 7 | template '/etc/systemd/system/xsuportal-web-rust.service' do 8 | owner 'root' 9 | group 'root' 10 | mode '0644' 11 | notifies :run, 'execute[systemctl daemon-reload]' 12 | end 13 | 14 | template '/etc/systemd/system/xsuportal-api-rust.service' do 15 | owner 'root' 16 | group 'root' 17 | mode '0644' 18 | notifies :run, 'execute[systemctl daemon-reload]' 19 | end 20 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/templates/etc/systemd/system/xsuportal-api-golang.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=xsuportal-api-golang 3 | After=network.target mysql.service 4 | 5 | [Service] 6 | User=isucon 7 | ExecStart=/home/isucon/.x /home/isucon/webapp/golang/bin/benchmark_server 8 | WorkingDirectory=/home/isucon/webapp/golang 9 | EnvironmentFile=/home/isucon/env 10 | 11 | <%- if node[:xsuportal][:slice] -%> 12 | Slice=<%= node[:xsuportal][:slice] %> 13 | <%- end -%> 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/templates/etc/systemd/system/xsuportal-api-nodejs.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=xsuportal-api-nodejs 3 | After=network.target mysql.service 4 | 5 | [Service] 6 | User=isucon 7 | ExecStart=/home/isucon/.x npm run start-bench 8 | WorkingDirectory=/home/isucon/webapp/nodejs 9 | EnvironmentFile=/home/isucon/env 10 | 11 | <%- if node[:xsuportal][:slice] -%> 12 | Slice=<%= node[:xsuportal][:slice] %> 13 | <%- end -%> 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/templates/etc/systemd/system/xsuportal-api-ruby.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=xsuportal-api-ruby 3 | After=network.target mysql.service 4 | 5 | [Service] 6 | User=isucon 7 | ExecStart=/home/isucon/.x bundle exec ruby bin/benchmark_server.rb 8 | WorkingDirectory=/home/isucon/webapp/ruby 9 | EnvironmentFile=/home/isucon/env 10 | TimeoutStopSec=1 11 | 12 | <%- if node[:xsuportal][:slice] -%> 13 | Slice=<%= node[:xsuportal][:slice] %> 14 | <%- end -%> 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/templates/etc/systemd/system/xsuportal-api-rust.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=xsuportal-api-rust 3 | After=network.target mysql.service 4 | 5 | [Service] 6 | User=isucon 7 | ExecStart=/home/isucon/.x /home/isucon/webapp/rust/target/release/benchmark_server 8 | WorkingDirectory=/home/isucon/webapp/rust 9 | EnvironmentFile=/home/isucon/env 10 | 11 | <%- if node[:xsuportal][:slice] -%> 12 | Slice=<%= node[:xsuportal][:slice] %> 13 | <%- end -%> 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/templates/etc/systemd/system/xsuportal-web-golang.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=xsuportal-web-golang 3 | After=network.target mysql.service 4 | 5 | [Service] 6 | User=isucon 7 | ExecStart=/home/isucon/.x /home/isucon/webapp/golang/bin/xsuportal 8 | WorkingDirectory=/home/isucon/webapp/golang 9 | EnvironmentFile=/home/isucon/env 10 | 11 | <%- if node[:xsuportal][:slice] -%> 12 | Slice=<%= node[:xsuportal][:slice] %> 13 | <%- end -%> 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/templates/etc/systemd/system/xsuportal-web-nodejs.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=xsuportal-api-nodejs 3 | After=network.target mysql.service 4 | 5 | [Service] 6 | User=isucon 7 | ExecStart=/home/isucon/.x npm run start 8 | WorkingDirectory=/home/isucon/webapp/nodejs 9 | EnvironmentFile=/home/isucon/env 10 | 11 | <%- if node[:xsuportal][:slice] -%> 12 | Slice=<%= node[:xsuportal][:slice] %> 13 | <%- end -%> 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/templates/etc/systemd/system/xsuportal-web-ruby.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=xsuportal-web-ruby 3 | After=network.target mysql.service 4 | 5 | [Service] 6 | User=isucon 7 | ExecStart=/home/isucon/.x bundle exec puma 8 | WorkingDirectory=/home/isucon/webapp/ruby 9 | EnvironmentFile=/home/isucon/env 10 | Environment=RACK_ENV=production 11 | 12 | <%- if node[:xsuportal][:slice] -%> 13 | Slice=<%= node[:xsuportal][:slice] %> 14 | <%- end -%> 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/templates/etc/systemd/system/xsuportal-web-rust.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=xsuportal-web-rust 3 | After=network.target mysql.service 4 | 5 | [Service] 6 | User=isucon 7 | ExecStart=/home/isucon/.x /home/isucon/webapp/rust/target/release/portal 8 | WorkingDirectory=/home/isucon/webapp/rust 9 | EnvironmentFile=/home/isucon/env 10 | 11 | <%- if node[:xsuportal][:slice] -%> 12 | Slice=<%= node[:xsuportal][:slice] %> 13 | <%- end -%> 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/templates/home/isucon/env: -------------------------------------------------------------------------------- 1 | MYSQL_HOSTNAME=localhost 2 | MYSQL_PORT=3306 3 | MYSQL_USER=isucon 4 | MYSQL_PASS=isucon 5 | MYSQL_DATABASE=xsuportal 6 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/tools.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'langs::ruby' 2 | 3 | 4 | execute "ls ~isucon/webapp; echo xxx; ls ~isucon/webapp/tools; ( cd ~isucon/webapp/tools && /home/isucon/.x bundle config set deployment true && /home/isucon/.x bundle config set path vendor/bundle && /home/isucon/.x bundle install --jobs 20 ) #{node[:xsuportal][:ignore_failed_build]['ruby']}" do 5 | user 'isucon' 6 | not_if 'cd ~isucon/webapp/tools && test -e .bundle && /home/isucon/.x bundle check' 7 | end 8 | 9 | execute 'cd ~isucon/webapp/tools && /home/isucon/.x bundle config set deployment "false"' do 10 | user 'isucon' 11 | end 12 | -------------------------------------------------------------------------------- /packer/files/itamae/cookbooks/xsuportal/web.rb: -------------------------------------------------------------------------------- 1 | include_cookbook 'tls-certificate' 2 | include_cookbook 'envoy' 3 | 4 | directory '/etc/ssl/private' do 5 | owner 'root' 6 | group 'root' 7 | mode '0755' 8 | end 9 | 10 | remote_file '/etc/envoy/config.yaml' do 11 | owner 'root' 12 | group 'root' 13 | mode '0644' 14 | end 15 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/base/default.rb: -------------------------------------------------------------------------------- 1 | # include_cookbook 'isucon-admin-user' 2 | include_cookbook 'isucon-user' 3 | include_cookbook 'systemd-timesyncd' 4 | include_cookbook 'langs::exec' 5 | 6 | %w( 7 | build-essential 8 | git-core 9 | unzip 10 | 11 | autoconf 12 | bison 13 | pkg-config 14 | 15 | libbz2-dev 16 | libc-client2007e-dev 17 | libcurl4-openssl-dev 18 | libffi-dev 19 | libfreetype6-dev 20 | libjpeg-dev 21 | libkrb5-dev 22 | libonig-dev 23 | libpng-dev 24 | libreadline-dev 25 | libsqlite3-dev 26 | libssl-dev 27 | libtidy-dev 28 | libxml2-dev 29 | libxslt-dev 30 | libzip-dev 31 | zlib1g-dev 32 | ).each do |_| 33 | package _ 34 | end 35 | 36 | # Stop unattended upgrades 37 | %w( 38 | /etc/systemd/system/apt-daily-upgrade.timer 39 | /etc/systemd/system/apt-daily-upgrade.service 40 | /etc/systemd/system/apt-daily.timer 41 | /etc/systemd/system/apt-daily.service 42 | ).each do |_| 43 | link _ do 44 | to '/dev/null' 45 | force true 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/benchmarker/default.rb: -------------------------------------------------------------------------------- 1 | node.reverse_merge!( 2 | benchmarker: { 3 | enable: true, 4 | }, 5 | ) 6 | 7 | include_cookbook 'benchmarker' 8 | 9 | if node[:benchmarker][:enable] 10 | service 'isuxportal-supervisor.service' do 11 | action :enable 12 | end 13 | else 14 | service 'isuxportal-supervisor.service' do 15 | action :disable 16 | end 17 | end 18 | 19 | package 'dstat' 20 | package 'htop' 21 | 22 | include_cookbook 'prometheus-node-exporter' 23 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/ci/files/etc/systemd/system/benchmarker.slice: -------------------------------------------------------------------------------- 1 | # vim: ft=systemd 2 | [Unit] 3 | Description=benchmarker slice 4 | Before=slices.target 5 | 6 | [Slice] 7 | Slice=isucon.slice 8 | MemoryAccounting=yes 9 | IOAccounting=yes 10 | CPUAccounting=yes 11 | 12 | MemoryHigh=2048M 13 | 14 | CPUWeight=50 15 | # AllowedCPUs=0 1 16 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/ci/files/etc/systemd/system/contestant.slice: -------------------------------------------------------------------------------- 1 | # vim: ft=systemd 2 | [Unit] 3 | Description=contestant slice 4 | Before=slices.target 5 | 6 | [Slice] 7 | Slice=isucon.slice 8 | MemoryAccounting=yes 9 | IOAccounting=yes 10 | CPUAccounting=yes 11 | 12 | MemoryHigh=1024M 13 | MemoryMax=1200M 14 | 15 | CPUWeight=50 16 | # AllowedCPUs=2 17 | 18 | IOReadIOPSMax=/ 800 19 | IOWriteIOPSMax=/ 800 20 | IOReadBandwidthMax=/ 1024M 21 | IOWriteBandwidthMax=/ 1024M 22 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/ci/files/etc/systemd/system/isucon.slice: -------------------------------------------------------------------------------- 1 | # vim: ft=systemd 2 | [Unit] 3 | Description=isucon slice 4 | Before=slices.target 5 | 6 | [Slice] 7 | MemoryAccounting=yes 8 | IOAccounting=yes 9 | CPUAccounting=yes 10 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/ci/files/etc/systemd/system/mysql.service.d/slice.conf: -------------------------------------------------------------------------------- 1 | # vim: ft=systemd 2 | [Service] 3 | Slice=contestant.slice 4 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/ci/files/home/isucon/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | lang=$1 4 | 5 | if [ -e /home/isucon/builderror-$lang ]; then 6 | echo "build is failing..." 7 | cat /home/isucon/build.$lang.log || : 8 | exit 1 9 | fi 10 | 11 | sudo systemctl start xsuportal-web-${lang}.service 12 | sudo systemctl start xsuportal-api-${lang}.service 13 | 14 | sleep 5 15 | 16 | sudo systemd-run \ 17 | --working-directory=/home/isucon/benchmarker \ 18 | --pipe \ 19 | --wait \ 20 | --collect \ 21 | --uid=$(id -u)\ 22 | --gid=$(id -g) \ 23 | --slice=benchmarker.slice \ 24 | --service-type=oneshot \ 25 | -p AmbientCapabilities=CAP_NET_BIND_SERVICE \ 26 | -p CapabilityBoundingSet=CAP_NET_BIND_SERVICE \ 27 | -p LimitNOFILE=2000000 \ 28 | -p TimeoutStartSec=110s \ 29 | ~isucon/benchmarker/bin/benchmarker \ 30 | -exit-status \ 31 | -tls \ 32 | -target local.t.isucon.dev:443 \ 33 | -host-advertise localbench.t.isucon.dev \ 34 | -push-service-port 1001 \ 35 | -tls-cert /etc/ssl/private/tls-cert.pem \ 36 | -tls-key /etc/ssl/private/tls-key.pem \ 37 | > ~isucon/ci.log 2>&1 38 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/contestant/default.rb: -------------------------------------------------------------------------------- 1 | default_enable = 'ruby' 2 | 3 | node.reverse_merge!( 4 | xsuportal: { 5 | enable: default_enable, 6 | disable_default: false, 7 | }, 8 | ) 9 | include_cookbook 'grub' if node[:is_ec2] 10 | include_cookbook 'xsuportal' 11 | 12 | if node[:xsuportal][:enable] 13 | service "xsuportal-web-#{node[:xsuportal][:enable]}.service" do 14 | action :enable 15 | end 16 | 17 | service "xsuportal-api-#{node[:xsuportal][:enable]}.service" do 18 | action :enable 19 | end 20 | end 21 | 22 | if node[:xsuportal][:disable_default] # for ci role 23 | service "xsuportal-web-#{default_enable}.service" do 24 | action :disable 25 | end 26 | 27 | service "xsuportal-api-#{default_enable}.service" do 28 | action :disable 29 | end 30 | end 31 | 32 | 33 | service "envoy.service" do 34 | #TODO:action [:enable, :start] 35 | action :enable 36 | end 37 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/full/default.rb: -------------------------------------------------------------------------------- 1 | node.reverse_merge!( 2 | benchmarker: { 3 | enable: false, 4 | slice: 'benchmarker.slice', 5 | }, 6 | xsuportal: { 7 | enable: nil, 8 | disable_default: true, 9 | slice: 'contestant.slice', 10 | }, 11 | envoy: { 12 | slice: 'contestant.slice', 13 | }, 14 | cmdline: { 15 | maxcpus: nil, 16 | mem: nil, 17 | }, 18 | ) 19 | 20 | include_role 'contestant' 21 | include_role 'benchmarker' 22 | 23 | remote_file '/etc/systemd/system/isucon.slice' do 24 | owner 'root' 25 | group 'root' 26 | mode '0644' 27 | notifies :run, 'execute[systemctl daemon-reload]' 28 | end 29 | 30 | 31 | remote_file '/etc/systemd/system/contestant.slice' do 32 | owner 'root' 33 | group 'root' 34 | mode '0644' 35 | notifies :run, 'execute[systemctl daemon-reload]' 36 | end 37 | 38 | remote_file '/etc/systemd/system/benchmarker.slice' do 39 | owner 'root' 40 | group 'root' 41 | mode '0644' 42 | notifies :run, 'execute[systemctl daemon-reload]' 43 | end 44 | 45 | directory '/etc/systemd/system/mysql.service.d' do 46 | owner 'root' 47 | group 'root' 48 | mode '0755' 49 | end 50 | 51 | remote_file '/etc/systemd/system/mysql.service.d/slice.conf' do 52 | owner 'root' 53 | group 'root' 54 | mode '0644' 55 | notifies :run, 'execute[systemctl daemon-reload]' 56 | end 57 | 58 | 59 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/full/files/etc/systemd/system/benchmarker.slice: -------------------------------------------------------------------------------- 1 | # vim: ft=systemd 2 | [Unit] 3 | Description=benchmarker slice 4 | Before=slices.target 5 | 6 | [Slice] 7 | Slice=isucon.slice 8 | MemoryAccounting=yes 9 | IOAccounting=yes 10 | CPUAccounting=yes 11 | 12 | MemoryHigh=2048M 13 | 14 | CPUWeight=50 15 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/full/files/etc/systemd/system/contestant.slice: -------------------------------------------------------------------------------- 1 | # vim: ft=systemd 2 | [Unit] 3 | Description=contestant slice 4 | Before=slices.target 5 | 6 | [Slice] 7 | Slice=isucon.slice 8 | MemoryAccounting=yes 9 | IOAccounting=yes 10 | CPUAccounting=yes 11 | 12 | MemoryHigh=1024M 13 | MemoryMax=1200M 14 | 15 | CPUWeight=50 16 | 17 | IOReadIOPSMax=/ 400 18 | IOWriteIOPSMax=/ 400 19 | IOReadBandwidthMax=/ 15M 20 | IOWriteBandwidthMax=/ 15M 21 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/full/files/etc/systemd/system/isucon.slice: -------------------------------------------------------------------------------- 1 | # vim: ft=systemd 2 | [Unit] 3 | Description=isucon slice 4 | Before=slices.target 5 | 6 | [Slice] 7 | MemoryAccounting=yes 8 | IOAccounting=yes 9 | CPUAccounting=yes 10 | -------------------------------------------------------------------------------- /packer/files/itamae/roles/full/files/etc/systemd/system/mysql.service.d/slice.conf: -------------------------------------------------------------------------------- 1 | # vim: ft=systemd 2 | [Service] 3 | Slice=contestant.slice 4 | -------------------------------------------------------------------------------- /packer/files/itamae/site.rb: -------------------------------------------------------------------------------- 1 | include_recipe './site/functions.rb' 2 | include_recipe './site/mitamae_ext.rb' 3 | 4 | include_role 'base' 5 | -------------------------------------------------------------------------------- /packer/files/itamae/site/functions.rb: -------------------------------------------------------------------------------- 1 | execute "systemctl daemon-reload" do 2 | action :nothing 3 | end 4 | 5 | execute "apt-get update" do 6 | action :nothing 7 | end 8 | 9 | node[:is_ec2] = run_command('grep -q "Amazon EC2" /sys/class/dmi/id/sys_vendor', error: false).exit_status == 0 10 | -------------------------------------------------------------------------------- /packer/files/itamae/site/mitamae_ext.rb: -------------------------------------------------------------------------------- 1 | MItamae::RecipeContext.class_eval do 2 | ROLES_DIR = File.expand_path("../../roles", __FILE__) 3 | def include_role(name) 4 | names = name.split("::") 5 | 6 | names << "default" if names.length == 1 7 | names[-1] += ".rb" 8 | 9 | common_candidates = [ 10 | File.join(ROLES_DIR, *names), 11 | ] 12 | candidates = [ 13 | *common_candidates, 14 | ] 15 | 16 | candidates.each do |candidate| 17 | if File.exist?(candidate) 18 | include_recipe(candidate) 19 | return 20 | end 21 | end 22 | raise "Role #{name} couldn't found" 23 | end 24 | 25 | COOKBOOKS_DIR = File.expand_path("../../cookbooks", __FILE__) 26 | def include_cookbook(name) 27 | names = name.split("::") 28 | 29 | names << "default" if names.length == 1 30 | names[-1] += ".rb" 31 | 32 | common_candidates = [ 33 | File.join(COOKBOOKS_DIR, *names), 34 | ] 35 | candidates = [ 36 | *common_candidates, 37 | ] 38 | candidates.each do |candidate| 39 | if File.exist?(candidate) 40 | include_recipe(candidate) 41 | return 42 | end 43 | end 44 | raise "Cookbook #{name} couldn't found" 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /packer/files/sources-ec2.list: -------------------------------------------------------------------------------- 1 | deb http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/ focal main restricted 2 | deb http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/ focal-updates main restricted 3 | deb http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/ focal universe 4 | deb http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/ focal-updates universe 5 | deb http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/ focal multiverse 6 | deb http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/ focal-updates multiverse 7 | deb http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse 8 | 9 | deb http://security.ubuntu.com/ubuntu focal-security main restricted 10 | deb http://security.ubuntu.com/ubuntu focal-security universe 11 | deb http://security.ubuntu.com/ubuntu focal-security multiverse 12 | -------------------------------------------------------------------------------- /packer/files/sources-generic.list: -------------------------------------------------------------------------------- 1 | deb http://jp.archive.ubuntu.com/ubuntu/ focal main restricted 2 | deb http://jp.archive.ubuntu.com/ubuntu/ focal-updates main restricted 3 | deb http://jp.archive.ubuntu.com/ubuntu/ focal universe 4 | deb http://jp.archive.ubuntu.com/ubuntu/ focal-updates universe 5 | deb http://jp.archive.ubuntu.com/ubuntu/ focal multiverse 6 | deb http://jp.archive.ubuntu.com/ubuntu/ focal-updates multiverse 7 | deb http://jp.archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse 8 | 9 | deb http://security.ubuntu.com/ubuntu focal-security main restricted 10 | deb http://security.ubuntu.com/ubuntu focal-security universe 11 | deb http://security.ubuntu.com/ubuntu focal-security multiverse 12 | -------------------------------------------------------------------------------- /packer/full.jsonnet: -------------------------------------------------------------------------------- 1 | local base = import './base.libsonnet'; 2 | 3 | base { 4 | arg_variant: 'full', 5 | } 6 | -------------------------------------------------------------------------------- /packer/qemu-http/meta-data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon10-final/9b43f02b6248610eaf786fa9f25396437dd1ffca/packer/qemu-http/meta-data -------------------------------------------------------------------------------- /packer/qemu-http/user-data: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | password: ubuntu 3 | ssh_pwauth: true 4 | chpasswd: 5 | expire: false 6 | -------------------------------------------------------------------------------- /packer/scripts/latest.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'aws-sdk-s3' 3 | 4 | BUCKET = 'isucon10-machine-images' 5 | PREFIX = 'final/' 6 | 7 | do_presign = ARGV.delete('--presign') 8 | 9 | s3 = Aws::S3::Client.new(use_dualstack_endpoint: true) 10 | 11 | manifest_obj = s3.list_objects_v2(bucket: BUCKET, prefix: "#{PREFIX}#{ARGV[0]}/") 12 | .flat_map(&:contents) 13 | .select { |_| _.key.match?(/\.json$/) } 14 | .sort_by { |_| _.last_modified } 15 | .last 16 | manifest = JSON.parse(s3.get_object(bucket: BUCKET, key: manifest_obj.key).body.read) 17 | 18 | qcow2_key = manifest.fetch('qcow2_key') 19 | qcow2_sha256 = manifest.fetch('qcow2_sha256') 20 | 21 | url = Aws::S3::Presigner.new(client: s3).presigned_url(:get_object, bucket: BUCKET, key: qcow2_key) 22 | if do_presign 23 | puts({url: url, sum: qcow2_sha256}.to_json) 24 | else 25 | File.write "tmp/latest-#{ARGV[0]}.sum", "#{qcow2_sha256}\n" 26 | system("curl", "-fo", "tmp/latest-#{ARGV[0]}.qcow2", url, exception: true) 27 | end 28 | -------------------------------------------------------------------------------- /packer/scripts/prune.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'aws-sdk-s3' 3 | require 'aws-sdk-ec2' 4 | 5 | BUCKET = 'isucon10-machine-images' 6 | PREFIX = 'final/' 7 | KEEP_NUM = 15 8 | 9 | s3 = Aws::S3::Client.new(use_dualstack_endpoint: true) 10 | ec2 = Aws::EC2::Client.new() 11 | 12 | manifest = JSON.parse(ARGF.read) 13 | family = manifest.fetch('family') 14 | 15 | prefix = "#{PREFIX}#{family}/" 16 | 17 | manifest_objs = s3.list_objects_v2(bucket: BUCKET, prefix: prefix) 18 | .flat_map(&:contents) 19 | .select { |_| _.key.match?(/\.json$/) } 20 | .sort_by { |_| _.last_modified } 21 | 22 | manifest_objs[0...-KEEP_NUM].each do |manifest_obj| 23 | puts "===> Pruning s3://#{BUCKET}/#{manifest_obj.key}" 24 | target_manifest = JSON.parse(s3.get_object(bucket: BUCKET, key: manifest_obj.key).body.read) 25 | 26 | # Delete qcow2 27 | qcow2_key = target_manifest['qcow2_key'] 28 | if qcow2_key 29 | puts " * Delete qcow2: #{qcow2_key}" 30 | begin 31 | s3.delete_object(bucket: BUCKET, key: qcow2_key) 32 | rescue Aws::S3::Errors::NotFound => e 33 | puts " > it was already deleted. (#{e.inspect})" 34 | end 35 | end 36 | 37 | # Deregister AMI 38 | ami_id = target_manifest['ami_id'] 39 | if ami_id 40 | puts " * Deregister AMI: #{ami_id}" 41 | image = ec2.describe_images(image_ids: [ami_id]).images[0] 42 | if image 43 | snapshot_ids = image.block_device_mappings.map do |mapping| 44 | mapping.ebs && mapping.ebs.snapshot_id 45 | end.compact 46 | 47 | ec2.deregister_image(image_id: ami_id) 48 | 49 | snapshot_ids.each do |snapshot_id| 50 | puts " * Delete snapshot #{snapshot_id} " 51 | ec2.delete_snapshot(snapshot_id: snapshot_id) 52 | end 53 | else 54 | puts " > it was already gone." 55 | end 56 | end 57 | 58 | puts " * Delete manifest" 59 | s3.delete_object(bucket: BUCKET, key: manifest_obj.key) 60 | end 61 | -------------------------------------------------------------------------------- /packer/scripts/upload.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'aws-sdk-s3' 3 | 4 | BUCKET = 'isucon10-machine-images' 5 | PREFIX = 'final/' 6 | 7 | manifest = JSON.parse(File.read(ARGV[0])) 8 | builds = manifest.fetch('builds') 9 | 10 | ami = builds.find { |_| _.fetch('name') == 'amazon-ebs' } 11 | qemu = builds.find { |_| _.fetch('name') == 'qemu' } 12 | 13 | name = manifest['name'] = (qemu || ami).fetch('custom_data').fetch('name') 14 | family = manifest['family'] = (qemu || ami).fetch('custom_data').fetch('family') 15 | 16 | manifest['ami_id'] = ami.fetch('artifact_id').split(?:,2)[1] if ami 17 | 18 | manifest_key = manifest['manifest_key'] = "#{PREFIX}#{family}/#{name}.json" 19 | qcow2_path = "output/#{name}/#{name}" 20 | qcow2_key = manifest['qcow2_key'] = "#{PREFIX}#{family}/#{name}.qcow2" if qemu 21 | 22 | s3 = Aws::S3::Client.new(use_dualstack_endpoint: true) 23 | 24 | puts "==> loading manifest " 25 | puts " #{manifest.to_json}" 26 | 27 | if qemu 28 | puts "==> uploading qcow2" 29 | puts " * Source: #{qcow2_path}" 30 | puts " * Destination: s3://#{BUCKET}/#{qcow2_key}" 31 | checksum = manifest['qcow2_sha256'] = Digest::SHA256.file(qcow2_path) 32 | puts " * Checksum: #{checksum}" 33 | begin 34 | File.open(qcow2_path, 'rb') do |io| 35 | s3.put_object(bucket: BUCKET, key: qcow2_key, body: io) 36 | end 37 | rescue Aws::S3::Errors::EntityTooLarge => e 38 | puts " > using aws-cli (#{e.inspect})" 39 | system("aws", "s3", "cp", "--quiet", qcow2_path, "s3://#{BUCKET}/#{qcow2_key}", exception: true) 40 | end 41 | end 42 | 43 | puts "==> uploading manifest" 44 | puts " * Destination: s3://#{BUCKET}/#{manifest_key}" 45 | s3.put_object(bucket: BUCKET, key: manifest_key, body: JSON.pretty_generate(manifest)) 46 | File.write ARGV[0], JSON.pretty_generate(manifest) 47 | -------------------------------------------------------------------------------- /proto/xsuportal/error.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal"; 4 | 5 | message Error { 6 | int32 code = 1; 7 | string name = 2; 8 | string human_message = 3; 9 | repeated string human_descriptions = 4; 10 | 11 | message DebugInfo { 12 | string exception = 1; 13 | repeated string trace = 2; 14 | repeated string application_trace = 3; 15 | repeated string framework_trace = 4; 16 | } 17 | DebugInfo debug_info = 16; 18 | } 19 | -------------------------------------------------------------------------------- /proto/xsuportal/resources/benchmark_job.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.resources; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/resources"; 4 | 5 | import "xsuportal/resources/benchmark_result.proto"; 6 | import "xsuportal/resources/team.proto"; 7 | 8 | import "google/protobuf/timestamp.proto"; 9 | 10 | message BenchmarkJob { 11 | int64 id = 1; 12 | int64 team_id = 2; 13 | // int64 target_id = 3; 14 | Status status = 4; 15 | enum Status { 16 | PENDING = 0; 17 | SENT = 1; 18 | RUNNING = 2; 19 | ERRORED = 3; 20 | CANCELLED = 4; 21 | FINISHED = 5; 22 | } 23 | 24 | google.protobuf.Timestamp created_at = 5; 25 | google.protobuf.Timestamp updated_at = 6; 26 | google.protobuf.Timestamp started_at = 7; 27 | google.protobuf.Timestamp finished_at = 8; 28 | 29 | // int64 score = 9; 30 | // // instance_name is not available for contestant 31 | // string instance_name = 10; 32 | 33 | Team team = 16; 34 | // target & result are only available at GetBenchmarkJobResponse 35 | // ContestantInstance target = 17; 36 | BenchmarkResult result = 18; 37 | 38 | string target_hostname = 30; 39 | } 40 | -------------------------------------------------------------------------------- /proto/xsuportal/resources/benchmark_result.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.resources; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/resources"; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | message BenchmarkResult { 8 | bool finished = 1; 9 | bool passed = 2; 10 | int64 score = 3; 11 | ScoreBreakdown score_breakdown = 4; 12 | message ScoreBreakdown { 13 | int64 raw = 1; 14 | int64 deduction = 2; 15 | } 16 | 17 | string reason = 5; 18 | google.protobuf.Timestamp marked_at = 6; 19 | // string stdout = 6; 20 | // string stderr = 7; 21 | 22 | // Survey survey = 8; 23 | 24 | // message Survey { 25 | // string language = 1; 26 | // } 27 | } 28 | -------------------------------------------------------------------------------- /proto/xsuportal/resources/clarification.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.resources; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/resources"; 4 | 5 | import "xsuportal/resources/team.proto"; 6 | import "google/protobuf/timestamp.proto"; 7 | 8 | message Clarification { 9 | int64 id = 1; 10 | int64 team_id = 2; 11 | bool answered = 3; 12 | bool disclosed = 4; 13 | string question = 5; 14 | string answer = 6; 15 | google.protobuf.Timestamp created_at = 7; 16 | google.protobuf.Timestamp answered_at = 8; 17 | 18 | Team team = 16; 19 | } 20 | -------------------------------------------------------------------------------- /proto/xsuportal/resources/contest.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.resources; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/resources"; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | message Contest { 8 | google.protobuf.Timestamp registration_open_at = 1; 9 | google.protobuf.Timestamp contest_starts_at = 3; 10 | google.protobuf.Timestamp contest_freezes_at = 4; 11 | google.protobuf.Timestamp contest_ends_at = 5; 12 | 13 | enum Status { 14 | STANDBY = 0; 15 | REGISTRATION = 1; 16 | STARTED = 2; 17 | FINISHED = 3; 18 | } 19 | Status status = 6; 20 | bool frozen = 7; 21 | } 22 | -------------------------------------------------------------------------------- /proto/xsuportal/resources/contestant.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.resources; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/resources"; 4 | 5 | message Contestant { 6 | string id = 1; 7 | int64 team_id = 2; 8 | string name = 3; 9 | bool is_student = 4; 10 | bool is_staff = 5; 11 | } 12 | -------------------------------------------------------------------------------- /proto/xsuportal/resources/leaderboard.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.resources; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/resources"; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | import "xsuportal/resources/team.proto"; 7 | import "xsuportal/resources/contest.proto"; 8 | 9 | message Leaderboard { 10 | message LeaderboardItem { 11 | message LeaderboardScore { 12 | int64 score = 1; 13 | google.protobuf.Timestamp started_at = 2; 14 | google.protobuf.Timestamp marked_at = 3; 15 | } 16 | repeated LeaderboardScore scores = 1; 17 | LeaderboardScore best_score = 2; 18 | LeaderboardScore latest_score = 3; 19 | int64 finish_count = 4; 20 | Team team = 16; 21 | } 22 | repeated LeaderboardItem teams = 1; 23 | repeated LeaderboardItem general_teams = 2; 24 | repeated LeaderboardItem student_teams = 3; 25 | repeated LeaderboardItem progresses = 4; 26 | 27 | Contest contest = 5; 28 | } 29 | -------------------------------------------------------------------------------- /proto/xsuportal/resources/notification.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.resources; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/resources"; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | message Notification { 8 | int64 id = 1; 9 | google.protobuf.Timestamp created_at = 2; 10 | 11 | message BenchmarkJobMessage { int64 benchmark_job_id = 1; } 12 | message ClarificationMessage { 13 | int64 clarification_id = 1; 14 | bool owned = 2; // True when a clarification is sent from a team of 15 | // notification recipient 16 | bool updated = 3; // True when a clarification was answered and have updated 17 | } 18 | message TestMessage { int64 something = 1; } 19 | oneof content { 20 | BenchmarkJobMessage content_benchmark_job = 3; 21 | ClarificationMessage content_clarification = 4; 22 | TestMessage content_test = 5; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /proto/xsuportal/resources/staff.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.resources; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/resources"; 4 | 5 | message Staff { 6 | int64 id = 1; 7 | string github_login = 2; 8 | } 9 | -------------------------------------------------------------------------------- /proto/xsuportal/resources/team.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.resources; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/resources"; 4 | 5 | import "xsuportal/resources/contestant.proto"; 6 | 7 | message Team { 8 | int64 id = 1; 9 | string name = 2; 10 | string leader_id = 3; 11 | repeated string member_ids = 4; 12 | bool withdrawn = 7; 13 | 14 | message StudentStatus { 15 | bool status = 1; 16 | } 17 | StudentStatus student = 10; 18 | 19 | message TeamDetail { 20 | string email_address = 1; 21 | 22 | string invite_token = 16; 23 | } 24 | TeamDetail detail = 8; 25 | 26 | Contestant leader = 16; 27 | repeated Contestant members = 17; 28 | } 29 | -------------------------------------------------------------------------------- /proto/xsuportal/services/admin/benchmark.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.admin; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/admin"; 4 | 5 | import "xsuportal/resources/benchmark_job.proto"; 6 | 7 | message ListBenchmarkJobsRequest { 8 | // optional filter by team_id 9 | int64 team_id = 1; 10 | // return only incomplete jobs 11 | bool incomplete_only = 2; 12 | 13 | } 14 | 15 | message ListBenchmarkJobsResponse { 16 | repeated xsuportal.proto.resources.BenchmarkJob jobs = 1; 17 | } 18 | 19 | message EnqueueBenchmarkJobRequest { 20 | int64 team_id = 1; 21 | // target ContestantInstance id 22 | int64 target_id = 2; 23 | } 24 | 25 | message EnqueueBenchmarkJobResponse { 26 | xsuportal.proto.resources.BenchmarkJob job = 1; 27 | } 28 | 29 | message CancelBenchmarkJobRequest { 30 | int64 id = 1; 31 | } 32 | 33 | message CancelBenchmarkJobResponse { 34 | xsuportal.proto.resources.BenchmarkJob job = 1; 35 | } 36 | 37 | // Query parameter 38 | message GetBenchmarkJobQuery { 39 | int64 id = 1; 40 | } 41 | 42 | message GetBenchmarkJobResponse { 43 | xsuportal.proto.resources.BenchmarkJob job = 1; 44 | } 45 | -------------------------------------------------------------------------------- /proto/xsuportal/services/admin/clarifications.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.admin; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/admin"; 4 | 5 | import "xsuportal/resources/clarification.proto"; 6 | 7 | message ListClarificationsRequest { 8 | // optional to filter 9 | int64 team_id = 1; 10 | } 11 | 12 | message ListClarificationsResponse { 13 | repeated xsuportal.proto.resources.Clarification clarifications = 1; 14 | } 15 | 16 | message GetClarificationRequest { 17 | int64 id = 1; 18 | } 19 | 20 | message GetClarificationResponse { 21 | xsuportal.proto.resources.Clarification clarification = 1; 22 | } 23 | 24 | message RespondClarificationRequest { 25 | int64 id = 1; 26 | bool disclose = 2; 27 | string answer = 3; 28 | // optional to override original question 29 | string question = 4; 30 | } 31 | 32 | message RespondClarificationResponse { 33 | xsuportal.proto.resources.Clarification clarification = 1; 34 | } 35 | 36 | message CreateClarificationRequest { 37 | string answer = 2; 38 | string question = 3; 39 | int64 team_id = 4; 40 | } 41 | 42 | message CreateClarificationResponse { 43 | xsuportal.proto.resources.Clarification clarification = 1; 44 | } 45 | -------------------------------------------------------------------------------- /proto/xsuportal/services/admin/dashboard.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.admin; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/admin"; 4 | 5 | import "xsuportal/resources/leaderboard.proto"; 6 | 7 | message DashboardRequest { 8 | } 9 | 10 | message DashboardResponse { 11 | xsuportal.proto.resources.Leaderboard leaderboard = 1; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /proto/xsuportal/services/admin/initialize.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.admin; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/admin"; 4 | 5 | import "xsuportal/resources/contest.proto"; 6 | 7 | message InitializeRequest { xsuportal.proto.resources.Contest contest = 1; } 8 | 9 | message InitializeResponse { 10 | message BenchmarkServer { 11 | string host = 1; 12 | int64 port = 2; 13 | } 14 | // 実装言語 15 | string language = 1; 16 | // 実ベンチマーカーに伝える仮想ベンチマークサーバー(gRPC)のホスト情報 17 | BenchmarkServer benchmark_server = 2; 18 | } 19 | -------------------------------------------------------------------------------- /proto/xsuportal/services/admin/teams.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.admin; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/admin"; 4 | 5 | import "xsuportal/resources/team.proto"; 6 | import "xsuportal/resources/contestant.proto"; 7 | 8 | message ListTeamsRequest { 9 | } 10 | 11 | message ListTeamsResponse { 12 | repeated TeamListItem teams = 1; 13 | message TeamListItem { 14 | int64 team_id = 1; 15 | string name = 2; 16 | repeated string member_names = 3; 17 | bool is_student = 5; 18 | bool withdrawn = 6; 19 | } 20 | } 21 | 22 | message GetTeamRequest { 23 | int64 id = 1; 24 | } 25 | 26 | message GetTeamResponse { 27 | xsuportal.proto.resources.Team team = 1; 28 | } 29 | 30 | message UpdateTeamRequest { 31 | xsuportal.proto.resources.Team team = 1; 32 | repeated xsuportal.proto.resources.Contestant contestants = 2; 33 | } 34 | 35 | message UpdateTeamResponse { 36 | } 37 | -------------------------------------------------------------------------------- /proto/xsuportal/services/audience/dashboard.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.audience; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/audience"; 4 | 5 | import "xsuportal/resources/leaderboard.proto"; 6 | 7 | message DashboardRequest {} 8 | 9 | message DashboardResponse { 10 | xsuportal.proto.resources.Leaderboard leaderboard = 1; 11 | } 12 | -------------------------------------------------------------------------------- /proto/xsuportal/services/audience/team_list.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.audience; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/audience"; 4 | 5 | message ListTeamsResponse { 6 | repeated TeamListItem teams = 1; 7 | message TeamListItem { 8 | int64 team_id = 1; 9 | string name = 2; 10 | repeated string member_names = 3; 11 | bool is_student = 5; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /proto/xsuportal/services/bench/receiving.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.bench; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/bench"; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | service BenchmarkQueue { 8 | rpc ReceiveBenchmarkJob(ReceiveBenchmarkJobRequest) 9 | returns (ReceiveBenchmarkJobResponse); 10 | } 11 | 12 | message ReceiveBenchmarkJobRequest { 13 | // string token = 1; 14 | // string instance_name = 2; 15 | int64 team_id = 3; 16 | } 17 | 18 | message ReceiveBenchmarkJobResponse { 19 | message JobHandle { 20 | int64 job_id = 1; 21 | string handle = 2; 22 | string target_hostname = 3; 23 | // string description_human = 4; 24 | google.protobuf.Timestamp contest_started_at = 10; 25 | google.protobuf.Timestamp job_created_at = 11; 26 | } 27 | // optional 28 | JobHandle job_handle = 1; 29 | } 30 | -------------------------------------------------------------------------------- /proto/xsuportal/services/bench/reporting.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.bench; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/bench"; 4 | 5 | import "xsuportal/resources/benchmark_result.proto"; 6 | 7 | service BenchmarkReport { 8 | rpc ReportBenchmarkResult(stream ReportBenchmarkResultRequest) 9 | returns (stream ReportBenchmarkResultResponse); 10 | } 11 | 12 | message ReportBenchmarkResultRequest { 13 | int64 job_id = 1; 14 | string handle = 2; 15 | int64 nonce = 3; 16 | xsuportal.proto.resources.BenchmarkResult result = 4; 17 | } 18 | 19 | message ReportBenchmarkResultResponse { int64 acked_nonce = 1; } 20 | -------------------------------------------------------------------------------- /proto/xsuportal/services/common/me.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.common; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/common"; 4 | 5 | import "xsuportal/resources/team.proto"; 6 | import "xsuportal/resources/contestant.proto"; 7 | import "xsuportal/resources/contest.proto"; 8 | 9 | message GetCurrentSessionResponse { 10 | xsuportal.proto.resources.Team team = 1; 11 | xsuportal.proto.resources.Contestant contestant = 2; 12 | xsuportal.proto.resources.Contest contest = 4; 13 | string push_vapid_key = 6; 14 | } 15 | -------------------------------------------------------------------------------- /proto/xsuportal/services/contestant/benchmark.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package xsuportal.proto.services.contestant; 4 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/contestant"; 5 | 6 | import "xsuportal/resources/benchmark_job.proto"; 7 | 8 | message ListBenchmarkJobsRequest {} 9 | 10 | message ListBenchmarkJobsResponse { 11 | repeated xsuportal.proto.resources.BenchmarkJob jobs = 1; 12 | } 13 | 14 | message EnqueueBenchmarkJobRequest { 15 | // target ContestantInstance id 16 | // int64 target_id = 1; 17 | string target_hostname = 10; 18 | } 19 | 20 | message EnqueueBenchmarkJobResponse { 21 | xsuportal.proto.resources.BenchmarkJob job = 1; 22 | } 23 | 24 | // Query parameter 25 | message GetBenchmarkJobQuery { int64 id = 1; } 26 | 27 | message GetBenchmarkJobResponse { 28 | xsuportal.proto.resources.BenchmarkJob job = 1; 29 | } 30 | -------------------------------------------------------------------------------- /proto/xsuportal/services/contestant/clarifications.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.contestant; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/contestant"; 4 | 5 | import "xsuportal/resources/clarification.proto"; 6 | 7 | message ListClarificationsRequest { 8 | } 9 | 10 | message ListClarificationsResponse { 11 | repeated xsuportal.proto.resources.Clarification clarifications = 1; 12 | } 13 | 14 | message RequestClarificationRequest { 15 | string question = 1; 16 | } 17 | 18 | message RequestClarificationResponse { 19 | xsuportal.proto.resources.Clarification clarification = 1; 20 | } 21 | -------------------------------------------------------------------------------- /proto/xsuportal/services/contestant/dashboard.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.contestant; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/contestant"; 4 | 5 | import "xsuportal/resources/leaderboard.proto"; 6 | 7 | message DashboardRequest {} 8 | 9 | message DashboardResponse { 10 | xsuportal.proto.resources.Leaderboard leaderboard = 1; 11 | } 12 | -------------------------------------------------------------------------------- /proto/xsuportal/services/contestant/login.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.contestant; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/contestant"; 4 | 5 | message LoginRequest { 6 | string contestant_id = 1; 7 | string password = 2; 8 | } 9 | 10 | message LoginResponse {} 11 | -------------------------------------------------------------------------------- /proto/xsuportal/services/contestant/logout.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.contestant; 3 | option go_package = "github.com/isucon/isucon10-final/proto/services/contestant"; 4 | 5 | message LogoutRequest {} 6 | 7 | message LogoutResponse {} 8 | -------------------------------------------------------------------------------- /proto/xsuportal/services/contestant/notifications.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.contestant; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/contestant"; 4 | 5 | import "xsuportal/resources/notification.proto"; 6 | 7 | message ListNotificationsQuery { 8 | // Last notifications.id that a user-agent has received through 9 | // ListNotificationsQuery during a current session. If not specified (=0), 10 | // uses server-side `read` column as a hint. 11 | int64 after = 1; 12 | } 13 | 14 | message ListNotificationsResponse { 15 | int64 last_answered_clarification_id = 1; 16 | repeated xsuportal.proto.resources.Notification notifications = 2; 17 | } 18 | 19 | message SubscribeNotificationRequest { 20 | string endpoint = 1; 21 | string p256dh = 2; 22 | string auth = 3; 23 | } 24 | 25 | message SubscribeNotificationResponse {} 26 | 27 | message UnsubscribeNotificationRequest { string endpoint = 1; } 28 | 29 | message UnsubscribeNotificationResponse {} 30 | -------------------------------------------------------------------------------- /proto/xsuportal/services/contestant/signup.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.contestant; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/contestant"; 4 | 5 | message SignupRequest { 6 | string contestant_id = 1; 7 | string password = 2; 8 | } 9 | 10 | message SignupResponse {} 11 | -------------------------------------------------------------------------------- /proto/xsuportal/services/registration/create_team.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.registration; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/registration"; 4 | 5 | message CreateTeamRequest { 6 | string team_name = 1; 7 | string name = 2; // contestant name 8 | string email_address = 3; 9 | bool is_student = 4; 10 | } 11 | 12 | message CreateTeamResponse { 13 | int64 team_id = 1; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /proto/xsuportal/services/registration/join.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.registration; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/registration"; 4 | 5 | message JoinTeamRequest { 6 | int64 team_id = 1; 7 | string invite_token = 2; 8 | string name = 3; 9 | bool is_student = 4; 10 | } 11 | 12 | message JoinTeamResponse { 13 | } 14 | -------------------------------------------------------------------------------- /proto/xsuportal/services/registration/session.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package xsuportal.proto.services.registration; 3 | option go_package = "github.com/isucon/isucon10-final/webapp/golang/proto/xsuportal/services/registration"; 4 | 5 | import "xsuportal/resources/team.proto"; 6 | 7 | // query parameter 8 | message GetRegistrationSessionQuery { 9 | int64 team_id = 1; 10 | string invite_token = 2; 11 | } 12 | 13 | message GetRegistrationSessionResponse { 14 | xsuportal.proto.resources.Team team = 1; 15 | enum Status { 16 | CLOSED = 0; 17 | NOT_JOINABLE = 1; 18 | NOT_LOGGED_IN = 2; 19 | CREATABLE = 3; 20 | JOINABLE = 4; 21 | JOINED = 5; 22 | } 23 | Status status = 2; 24 | string member_invite_url = 3; 25 | string invite_token = 4; 26 | } 27 | 28 | message UpdateRegistrationRequest { 29 | string team_name = 1; 30 | string name = 2; // contestant name 31 | string email_address = 3; 32 | bool is_student = 4; 33 | } 34 | 35 | message UpdateRegistrationResponse {} 36 | 37 | message DeleteRegistrationRequest {} 38 | 39 | message DeleteRegistrationResponse {} 40 | -------------------------------------------------------------------------------- /webapp/frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:prettier/recommended"], 3 | "plugins": ["@typescript-eslint"], 4 | "parser": "@typescript-eslint/parser", 5 | "parserOptions": { 6 | "sourceType": "module", 7 | "project": "./tsconfig.json" 8 | }, 9 | "rules": { 10 | "max-len": ["off"], 11 | "semi": ["error", "always"], 12 | "quotes": ["error", "double"], 13 | "@typescript-eslint/indent": ["warn", 2], 14 | "@typescript-eslint/explicit-member-accessibility": ["off"] 15 | } 16 | } -------------------------------------------------------------------------------- /webapp/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/ 3 | -------------------------------------------------------------------------------- /webapp/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |ID | 45 |Score | 46 |Status | 47 |Time | 48 |Duration | 49 |
---|
37 | エラー ({error.remoteError.code}):{" "} 38 | {error.remoteError.humanMessage}{" "} 39 |
40 |63 | {(error.remoteError.debugInfo.applicationTrace || []).join("\n")} 64 |65 | > 66 | ); 67 | } 68 | 69 | renderGenericError() { 70 | return ( 71 | <> 72 |
エラー: {this.props.error.name}
75 |