├── .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 | XSUCON Portal 5 | 6 | 7 | <%= htmlWebpackPlugin.tags.headTags %> 8 | 9 | 10 | 11 |
12 | <%= htmlWebpackPlugin.tags.bodyTags %> 13 | 14 | 15 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/BenchmarkJobList.tsx: -------------------------------------------------------------------------------- 1 | import { xsuportal } from "./pb"; 2 | 3 | import React from "react"; 4 | import { Link } from "react-router-dom"; 5 | 6 | import { TimeDuration } from "./TimeDuration"; 7 | import { Timestamp } from "./Timestamp"; 8 | import { BenchmarkJobStatus } from "./BenchmarkJobStatus"; 9 | 10 | export interface Props { 11 | list: xsuportal.proto.resources.IBenchmarkJob[]; 12 | } 13 | 14 | export const BenchmarkJobList: React.FC = (props: Props) => { 15 | const renderJob = ( 16 | job: xsuportal.proto.resources.IBenchmarkJob, 17 | i: number 18 | ) => { 19 | const id = job.id!.toString(); 20 | return ( 21 | 22 | 23 | 24 | #{id} 25 | 26 | 27 | {job.result?.score} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | }; 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {props.list.map(renderJob)} 52 |
IDScoreStatusTimeDuration
53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/BenchmarkJobStatus.tsx: -------------------------------------------------------------------------------- 1 | import { xsuportal } from "./pb"; 2 | 3 | import React from "react"; 4 | 5 | export interface Props { 6 | status: xsuportal.proto.resources.BenchmarkJob.Status; 7 | } 8 | 9 | const COLORS: { [key: string]: string } = { 10 | PENDING: "dark", 11 | RUNNING: "warning", 12 | CANCELLED: "info", 13 | FINISHED: "success", 14 | ERRORED: "danger", 15 | }; 16 | 17 | export const BenchmarkJobStatus: React.FC = (props: Props) => { 18 | const status = xsuportal.proto.resources.BenchmarkJob.Status[props.status]; 19 | const color = COLORS[status] || "light"; 20 | 21 | return {status}; 22 | }; 23 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/ErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | import { ApiError } from "./ApiClient"; 2 | import React from "react"; 3 | 4 | export interface Props { 5 | error: Error | ApiError; 6 | } 7 | 8 | export interface State {} 9 | 10 | export class ErrorMessage extends React.Component { 11 | constructor(props: Props) { 12 | super(props); 13 | this.state = {}; 14 | } 15 | 16 | public componentDidMount() { 17 | if (!(this.props.error instanceof ApiError)) { 18 | console.error(this.props.error); 19 | } 20 | } 21 | 22 | public render() { 23 | if (this.props.error instanceof ApiError) { 24 | return this.renderApiError(); 25 | } else { 26 | return this.renderGenericError(); 27 | } 28 | } 29 | renderApiError() { 30 | const error = this.props.error as ApiError; 31 | if (error.remoteError) { 32 | return ( 33 | <> 34 |
35 |
36 |

37 | エラー ({error.remoteError.code}):{" "} 38 | {error.remoteError.humanMessage}{" "} 39 |

40 |
41 |
42 |
    43 | {error.remoteError.humanDescriptions.map((v, i) => ( 44 |
  • {v}
  • 45 | ))} 46 |
47 | {this.renderDebugInfo()} 48 |
49 |
50 | 51 | ); 52 | } else { 53 | return this.renderGenericError(); 54 | } 55 | } 56 | 57 | renderDebugInfo() { 58 | const error = this.props.error as ApiError; 59 | if (!(error.remoteError && error.remoteError.debugInfo)) return; 60 | return ( 61 | <> 62 |
63 |           {(error.remoteError.debugInfo.applicationTrace || []).join("\n")}
64 |         
65 | 66 | ); 67 | } 68 | 69 | renderGenericError() { 70 | return ( 71 | <> 72 |
73 |
74 |

エラー: {this.props.error.name}

75 |
76 |
{this.props.error.message}
77 |
78 | 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/Logout.tsx: -------------------------------------------------------------------------------- 1 | import { xsuportal } from "./pb"; 2 | import { ApiError, ApiClient } from "./ApiClient"; 3 | import React from "react"; 4 | import { Redirect } from "react-router-dom"; 5 | 6 | import { ErrorMessage } from "./ErrorMessage"; 7 | 8 | export interface Props { 9 | session: xsuportal.proto.services.common.GetCurrentSessionResponse; 10 | client: ApiClient; 11 | } 12 | 13 | export interface State { 14 | session: xsuportal.proto.services.common.GetCurrentSessionResponse; 15 | error: Error | null; 16 | requesting: boolean; 17 | logoutSucceeded: boolean; 18 | } 19 | 20 | export class Logout extends React.Component { 21 | constructor(props: Props) { 22 | super(props); 23 | this.state = { 24 | session: this.props.session, 25 | error: null, 26 | requesting: false, 27 | logoutSucceeded: false, 28 | }; 29 | } 30 | 31 | public async componentDidMount() { 32 | if (this.state.requesting) return; 33 | try { 34 | this.setState({ requesting: true }); 35 | await this.logout(); 36 | const session = await this.props.client.getCurrentSession(); 37 | this.setState({ 38 | session: session, 39 | error: null, 40 | requesting: false, 41 | }); 42 | location.reload(); 43 | } catch (err) { 44 | this.setState({ error: err, requesting: false }); 45 | } 46 | } 47 | 48 | public render() { 49 | const currentContestant = this.state.session.contestant; 50 | if (!currentContestant) { 51 | return ( 52 | <> 53 | 54 | 55 | ); 56 | } else { 57 | return ( 58 | <> 59 |
60 |

ログアウト

61 |
62 |
{this.renderError()}
63 | 64 | ); 65 | } 66 | } 67 | 68 | public renderError() { 69 | if (!this.state.error) return; 70 | return ; 71 | } 72 | 73 | logout() { 74 | return this.props.client.logout({}); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/MaterialIcons.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Material Icons'), 6 | local('MaterialIcons-Regular'), 7 | url(./font/MaterialIcons-Regular.ttf) format('truetype'); 8 | } 9 | 10 | @font-face { 11 | font-family: 'Material Icons Outlined'; 12 | font-style: normal; 13 | font-weight: 400; 14 | src: local('Material Icons Outlined'), 15 | local('MaterialIconsOutlined-Regular'), 16 | url(./font/MaterialIconsOutlined-Regular.otf) format('opentype'); 17 | } 18 | 19 | .material-icons { 20 | font-family: 'Material Icons'; 21 | font-weight: normal; 22 | font-style: normal; 23 | font-size: 24px; /* Preferred icon size */ 24 | display: inline-block; 25 | line-height: 1; 26 | text-transform: none; 27 | letter-spacing: normal; 28 | word-wrap: normal; 29 | white-space: nowrap; 30 | direction: ltr; 31 | 32 | /* Support for all WebKit browsers. */ 33 | -webkit-font-smoothing: antialiased; 34 | /* Support for Safari and Chrome. */ 35 | text-rendering: optimizeLegibility; 36 | 37 | /* Support for Firefox. */ 38 | -moz-osx-font-smoothing: grayscale; 39 | 40 | /* Support for IE. */ 41 | font-feature-settings: 'liga'; 42 | } 43 | 44 | .material-icons-outlined { 45 | font-family: 'Material Icons Outlined'; 46 | font-weight: normal; 47 | font-style: normal; 48 | font-size: 24px; /* Preferred icon size */ 49 | display: inline-block; 50 | line-height: 1; 51 | text-transform: none; 52 | letter-spacing: normal; 53 | word-wrap: normal; 54 | white-space: nowrap; 55 | direction: ltr; 56 | 57 | /* Support for all WebKit browsers. */ 58 | -webkit-font-smoothing: antialiased; 59 | /* Support for Safari and Chrome. */ 60 | text-rendering: optimizeLegibility; 61 | 62 | /* Support for Firefox. */ 63 | -moz-osx-font-smoothing: grayscale; 64 | 65 | /* Support for IE. */ 66 | font-feature-settings: 'liga'; 67 | } 68 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/NavbarSession.ts: -------------------------------------------------------------------------------- 1 | import { xsuportal } from "./pb"; 2 | import { ApiClient } from "./ApiClient"; 3 | 4 | export function updateNavBarSession( 5 | session: xsuportal.proto.services.common.GetCurrentSessionResponse 6 | ) { 7 | if (session.contestant) { 8 | document.body.classList.add("xsu-session-user"); 9 | } else { 10 | document.body.classList.add("xsu-session-guest"); 11 | } 12 | console.log(session); 13 | } 14 | 15 | export async function fetchAndUpdateNavbarSession() { 16 | const client = new ApiClient(); 17 | updateNavBarSession(await client.getCurrentSession()); 18 | } 19 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/ReloadButton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export interface Props { 4 | requesting: boolean; 5 | onClick: () => any; 6 | } 7 | 8 | export const ReloadButton: React.FC = ({ requesting, onClick }) => { 9 | return ( 10 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/TeamPins.ts: -------------------------------------------------------------------------------- 1 | export type TeamPinsMap = Map; 2 | 3 | const loadPins = () => { 4 | const map: TeamPinsMap = new Map(); 5 | const item = window.localStorage.getItem("xsuportal-dashboard-pins"); 6 | if (item) { 7 | const teamIds: string[] = JSON.parse(item); 8 | for (const id of teamIds) { 9 | map.set(id, true); 10 | } 11 | } 12 | return map; 13 | }; 14 | 15 | const savePins = (pins: TeamPinsMap) => { 16 | const data = JSON.stringify(Array.from(pins.keys())); 17 | try { 18 | window.localStorage.setItem("xsuportal-dashboard-pins", data); 19 | } catch (e) { 20 | console.warn(e); 21 | } 22 | }; 23 | 24 | export class TeamPins { 25 | pins: TeamPinsMap; 26 | public onChange?: (newMap: TeamPinsMap) => void; 27 | 28 | constructor() { 29 | this.pins = loadPins(); 30 | this.set = this.set.bind(this); 31 | } 32 | 33 | public set(teamId: string, flag: boolean) { 34 | if (flag) { 35 | this.pins.set(teamId, true); 36 | } else { 37 | this.pins.delete(teamId); 38 | } 39 | savePins(this.pins); 40 | if (this.onChange) this.onChange(this.all()); 41 | } 42 | 43 | public all(): TeamPinsMap { 44 | return new Map(this.pins); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/TimeDuration.tsx: -------------------------------------------------------------------------------- 1 | import type { google } from "./pb"; 2 | 3 | import dayjs from "dayjs"; 4 | import durationPlugin from "dayjs/plugin/duration"; 5 | import relativeTimePlugin from "dayjs/plugin/relativeTime"; 6 | dayjs.extend(durationPlugin); 7 | dayjs.extend(relativeTimePlugin); 8 | 9 | import React from "react"; 10 | 11 | export interface Props { 12 | a: google.protobuf.ITimestamp; 13 | b?: google.protobuf.ITimestamp | undefined | null; 14 | } 15 | 16 | export const TimeDuration: React.FC = (props: Props) => { 17 | const tA = dayjs((props.a.seconds as number) * 1000 + (props.a.nanos as number) / 1000000); 18 | const tB = props.b ? dayjs((props.b.seconds as number) * 1000 + (props.b.nanos as number) / 1000000) : dayjs(); 19 | const d = dayjs.duration(tB.diff(tA)); 20 | return {d.humanize(false)}; 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/Timestamp.tsx: -------------------------------------------------------------------------------- 1 | import type { google } from "./pb"; 2 | import dayjs from "dayjs"; 3 | 4 | import React from "react"; 5 | 6 | export interface Props { 7 | timestamp: google.protobuf.ITimestamp; 8 | short?: boolean; 9 | } 10 | 11 | export const Timestamp: React.FC = (props: Props) => { 12 | const ts = props.timestamp; 13 | if (ts) { 14 | const t = dayjs( 15 | (ts.seconds as number) * 1000 + (ts.nanos as number) / 1000000 16 | ); 17 | return ( 18 | 21 | ); 22 | } else { 23 | return <>; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/admin/AdminNavbar.tsx: -------------------------------------------------------------------------------- 1 | import type { xsuportal } from "../pb"; 2 | import { AdminApiClient } from "./AdminApiClient"; 3 | 4 | import React from "react"; 5 | import { Link } from "react-router-dom"; 6 | 7 | import { ErrorMessage } from "../ErrorMessage"; 8 | 9 | export interface Props { 10 | session: xsuportal.proto.services.common.GetCurrentSessionResponse; 11 | client: AdminApiClient; 12 | } 13 | 14 | export interface State {} 15 | 16 | export class AdminNavbar extends React.Component { 17 | constructor(props: Props) { 18 | super(props); 19 | this.state = {}; 20 | } 21 | 22 | public render() { 23 | return ( 24 | 37 | ); 38 | } 39 | 40 | onLogout(e: React.MouseEvent) { 41 | e.preventDefault(); 42 | 43 | const form = document.createElement("form"); 44 | form.method = "POST"; 45 | form.action = "/session"; 46 | document.body.appendChild(form); 47 | 48 | const method = document.createElement("input"); 49 | method.type = "hidden"; 50 | method.name = "_method"; 51 | method.value = "delete"; 52 | form.appendChild(method); 53 | 54 | form.submit(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/font/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon10-final/9b43f02b6248610eaf786fa9f25396437dd1ffca/webapp/frontend/javascript/font/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /webapp/frontend/javascript/font/MaterialIconsOutlined-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon10-final/9b43f02b6248610eaf786fa9f25396437dd1ffca/webapp/frontend/javascript/font/MaterialIconsOutlined-Regular.otf -------------------------------------------------------------------------------- /webapp/frontend/javascript/packs/admin.tsx: -------------------------------------------------------------------------------- 1 | import "./application.scss"; 2 | import { ApiClient } from "../ApiClient"; 3 | import { AdminApp } from "../AdminApp"; 4 | import React from "react"; 5 | import ReactDOM from "react-dom"; 6 | 7 | (async function () { 8 | const client = new ApiClient(); 9 | const session = await client.getCurrentSession(); 10 | const elem = document.getElementById("app"); 11 | if (session.contestant?.isStaff) { 12 | ReactDOM.render(, elem); 13 | } else { 14 | location.href = "/"; 15 | } 16 | })(); 17 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/packs/application.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | @import "~bulma/bulma"; 3 | @import "../application.scss"; 4 | 5 | @import "~uplot/dist/uPlot.min"; 6 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/packs/audience.tsx: -------------------------------------------------------------------------------- 1 | import './application.scss'; 2 | import { ApiClient } from "../ApiClient"; 3 | import { updateNavBarSession } from "../NavbarSession"; 4 | import { AudienceApp } from "../AudienceApp"; 5 | import React from "react"; 6 | import ReactDOM from "react-dom"; 7 | 8 | (async function () { 9 | const client = new ApiClient(); 10 | const session = await client.getCurrentSession(); 11 | updateNavBarSession(session); 12 | const elem = document.getElementById("app"); 13 | ReactDOM.render(, elem); 14 | })(); 15 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/packs/contestant.tsx: -------------------------------------------------------------------------------- 1 | import "./application.scss"; 2 | import { ApiClient } from "../ApiClient"; 3 | import { updateNavBarSession } from "../NavbarSession"; 4 | import { ContestantApp } from "../ContestantApp"; 5 | import React from "react"; 6 | import ReactDOM from "react-dom"; 7 | 8 | (async function () { 9 | const client = new ApiClient(); 10 | const session = await client.getCurrentSession(); 11 | const release = document.querySelector( 12 | 'meta[name="xsu:release"]' 13 | )?.content; 14 | updateNavBarSession(session); 15 | const elem = document.getElementById("app"); 16 | if (session.team) { 17 | ReactDOM.render( 18 | , 19 | elem 20 | ); 21 | } else { 22 | location.href = "/"; 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /webapp/frontend/javascript/packs/navbar.ts: -------------------------------------------------------------------------------- 1 | import './application.scss'; 2 | import { fetchAndUpdateNavbarSession } from "../NavbarSession"; 3 | fetchAndUpdateNavbarSession(); 4 | -------------------------------------------------------------------------------- /webapp/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "webpack", 5 | "watch": "webpack -w" 6 | }, 7 | "devDependencies": { 8 | "@types/react": "^16.9.41", 9 | "@types/react-dom": "^16.9.8", 10 | "@types/react-router-dom": "^5.1.5", 11 | "@types/workbox-core": "^4.3.0", 12 | "css-loader": "^3.6.0", 13 | "eslint": "^7.4.0", 14 | "eslint-config-prettier": "^6.11.0", 15 | "eslint-plugin-prettier": "^3.1.4", 16 | "html-webpack-harddisk-plugin": "^1.0.2", 17 | "html-webpack-plugin": "^4.4.1", 18 | "mini-css-extract-plugin": "^0.9.0", 19 | "node-sass": "^4.14.1", 20 | "prettier": "^2.0.5", 21 | "sass-loader": "^9.0.1", 22 | "terser-webpack-plugin": "^3.0.6", 23 | "ts-loader": "^7.0.5", 24 | "typescript": "^3.9.6", 25 | "webpack": "^4.43.0", 26 | "webpack-assets-manifest": "^3.1.1", 27 | "webpack-cli": "^3.3.12", 28 | "workbox-webpack-plugin": "^5.1.4" 29 | }, 30 | "dependencies": { 31 | "bulma": "^0.9.0", 32 | "dayjs": "^1.8.33", 33 | "file-loader": "^6.1.0", 34 | "idb": "^5.0.6", 35 | "protobufjs": "~6.9.0", 36 | "react": "^16.13.1", 37 | "react-dom": "^16.13.1", 38 | "react-hook-form": "^6.4.0", 39 | "react-router-dom": "^5.2.0", 40 | "resolve-url-loader": "^3.1.1", 41 | "uplot": "~1.1.2", 42 | "workbox-core": "^5.1.4", 43 | "workbox-precaching": "^5.1.4" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /webapp/frontend/sw/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "moduleResolution": "node", 5 | "sourceMap": true, 6 | "strict": true, 7 | "allowSyntheticDefaultImports": true, 8 | "lib": ["es2018", "webworker"] 9 | }, 10 | "include": ["./"] 11 | } 12 | -------------------------------------------------------------------------------- /webapp/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "moduleResolution": "node", 5 | "sourceMap": true, 6 | "strict": true, 7 | "jsx": "react", 8 | "typeRoots": ["./node_modules/@types", "./types"], 9 | "allowSyntheticDefaultImports": true 10 | }, 11 | "exclude": ["sw/**/*"] 12 | } 13 | -------------------------------------------------------------------------------- /webapp/frontend/x41.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon10-final/9b43f02b6248610eaf786fa9f25396437dd1ffca/webapp/frontend/x41.ico -------------------------------------------------------------------------------- /webapp/generate_vapid_key.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | DST_PATH="$(cd $(dirname $0); pwd)/vapid_private.pem" 3 | if [ -f $DST_PATH ]; then 4 | echo "${DST_PATH} already exists" 5 | exit 1 6 | fi 7 | openssl ecparam -name prime256v1 -genkey -noout -out $DST_PATH 8 | echo "Created: ${DST_PATH}" 9 | 10 | -------------------------------------------------------------------------------- /webapp/golang/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /webapp/golang/Makefile: -------------------------------------------------------------------------------- 1 | PREFIX=$(PWD) 2 | BINDIR=$(PREFIX)/bin 3 | 4 | COMPILER=go 5 | 6 | GOFILES=$(wildcard ./*.go ./**/*.go) 7 | 8 | .PHONY: all 9 | all: clean build ## Cleanup and Build 10 | 11 | .PHONY: build 12 | build: $(GOFILES) ## Build executable files 13 | @GOBIN=$(BINDIR) $(COMPILER) install ./cmd/... 14 | 15 | 16 | .PHONY: clean 17 | clean: ## Cleanup files 18 | @$(RM) -r $(BINDIR) 19 | 20 | .PHONY: help 21 | help: ## Show help 22 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 23 | -------------------------------------------------------------------------------- /webapp/golang/db.go: -------------------------------------------------------------------------------- 1 | package xsuportal 2 | 3 | import ( 4 | "github.com/go-sql-driver/mysql" 5 | "github.com/jmoiron/sqlx" 6 | 7 | "github.com/isucon/isucon10-final/webapp/golang/util" 8 | ) 9 | 10 | func GetDB() (*sqlx.DB, error) { 11 | mysqlConfig := mysql.NewConfig() 12 | mysqlConfig.Net = "tcp" 13 | mysqlConfig.Addr = util.GetEnv("MYSQL_HOSTNAME", "127.0.0.1") + ":" + util.GetEnv("MYSQL_PORT", "3306") 14 | mysqlConfig.User = util.GetEnv("MYSQL_USER", "isucon") 15 | mysqlConfig.Passwd = util.GetEnv("MYSQL_PASS", "isucon") 16 | mysqlConfig.DBName = util.GetEnv("MYSQL_DATABASE", "xsuportal") 17 | mysqlConfig.Params = map[string]string{ 18 | "time_zone": "'+00:00'", 19 | } 20 | mysqlConfig.ParseTime = true 21 | 22 | return sqlx.Open("mysql", mysqlConfig.FormatDSN()) 23 | } 24 | -------------------------------------------------------------------------------- /webapp/golang/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/isucon/isucon10-final/webapp/golang 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/SherClockHolmes/webpush-go v1.1.2 7 | github.com/go-sql-driver/mysql v1.5.0 8 | github.com/golang/protobuf v1.4.2 9 | github.com/gorilla/sessions v1.2.1 10 | github.com/jmoiron/sqlx v1.2.1-0.20200615141059-0794cb1f47ee 11 | github.com/labstack/echo-contrib v0.9.0 12 | github.com/labstack/echo/v4 v4.1.17 13 | golang.org/x/crypto v0.0.0-20200930160638-afb6bcd081ae // indirect 14 | golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect 15 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect 16 | google.golang.org/grpc v1.32.0 17 | google.golang.org/protobuf v1.25.0 18 | ) 19 | -------------------------------------------------------------------------------- /webapp/golang/public: -------------------------------------------------------------------------------- 1 | ../frontend/public -------------------------------------------------------------------------------- /webapp/golang/util/get_env.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func GetEnv(key, val string) string { 8 | if v := os.Getenv(key); v == "" { 9 | return val 10 | } else { 11 | return v 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /webapp/nodejs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /webapp/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "gen": "grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=./proto --js_out=import_style=commonjs:./proto --grpc_out=./proto --proto_path=../../proto/ ../../proto/google/protobuf/* ../../proto/xsuportal/resources/* ../../proto/xsuportal/services/**/* ../../proto/xsuportal/error.proto", 9 | "dev": "ts-node src/bin/app.ts", 10 | "dev-bench": "ts-node src/bin/benchmark_server/main.ts", 11 | "send-webpush": "ts-node src/sendWebpush.ts", 12 | "start-bench": "node build/bin/benchmark_server/main.js", 13 | "build": "tsc --build ./tsconfig.json", 14 | "start": "node ./build/bin/app.js" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "devDependencies": { 20 | "@types/cookie-session": "^2.0.41", 21 | "@types/express": "^4.17.8", 22 | "@types/google-protobuf": "^3.7.3", 23 | "@types/mysql": "^2.15.15", 24 | "@types/node": "^14.11.2", 25 | "@types/sshpk": "^1.10.5", 26 | "@types/urlsafe-base64": "^1.0.28", 27 | "@types/web-push": "^3.3.0", 28 | "grpc-tools": "^1.9.1", 29 | "grpc_tools_node_protoc_ts": "^4.1.5", 30 | "ts-node": "^9.0.0", 31 | "typescript": "^4.0.3" 32 | }, 33 | "dependencies": { 34 | "@types/morgan": "^1.9.1", 35 | "@types/strftime": "^0.9.2", 36 | "cookie-session": "^1.4.0", 37 | "express": "^4.17.1", 38 | "google-protobuf": "^3.13.0", 39 | "grpc": "^1.24.3", 40 | "morgan": "^1.10.0", 41 | "promise-mysql": "^4.1.3", 42 | "sshpk": "^1.16.1", 43 | "strftime": "^0.10.0", 44 | "urlsafe-base64": "^1.0.0", 45 | "web-push": "^3.4.4" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /webapp/nodejs/proto/google/protobuf/timestamp_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/google/protobuf/timestamp_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: google.protobuf 2 | // file: google/protobuf/timestamp.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | 9 | export class Timestamp extends jspb.Message { 10 | getSeconds(): number; 11 | setSeconds(value: number): Timestamp; 12 | 13 | getNanos(): number; 14 | setNanos(value: number): Timestamp; 15 | 16 | 17 | serializeBinary(): Uint8Array; 18 | toObject(includeInstance?: boolean): Timestamp.AsObject; 19 | static toObject(includeInstance: boolean, msg: Timestamp): Timestamp.AsObject; 20 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 21 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 22 | static serializeBinaryToWriter(message: Timestamp, writer: jspb.BinaryWriter): void; 23 | static deserializeBinary(bytes: Uint8Array): Timestamp; 24 | static deserializeBinaryFromReader(message: Timestamp, reader: jspb.BinaryReader): Timestamp; 25 | } 26 | 27 | export namespace Timestamp { 28 | export type AsObject = { 29 | seconds: number, 30 | nanos: number, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/error_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/benchmark_job_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/benchmark_result_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/clarification_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/contest_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/contestant_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/contestant_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: xsuportal.proto.resources 2 | // file: xsuportal/resources/contestant.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | 9 | export class Contestant extends jspb.Message { 10 | getId(): string; 11 | setId(value: string): Contestant; 12 | 13 | getTeamId(): number; 14 | setTeamId(value: number): Contestant; 15 | 16 | getName(): string; 17 | setName(value: string): Contestant; 18 | 19 | getIsStudent(): boolean; 20 | setIsStudent(value: boolean): Contestant; 21 | 22 | getIsStaff(): boolean; 23 | setIsStaff(value: boolean): Contestant; 24 | 25 | 26 | serializeBinary(): Uint8Array; 27 | toObject(includeInstance?: boolean): Contestant.AsObject; 28 | static toObject(includeInstance: boolean, msg: Contestant): Contestant.AsObject; 29 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 30 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 31 | static serializeBinaryToWriter(message: Contestant, writer: jspb.BinaryWriter): void; 32 | static deserializeBinary(bytes: Uint8Array): Contestant; 33 | static deserializeBinaryFromReader(message: Contestant, reader: jspb.BinaryReader): Contestant; 34 | } 35 | 36 | export namespace Contestant { 37 | export type AsObject = { 38 | id: string, 39 | teamId: number, 40 | name: string, 41 | isStudent: boolean, 42 | isStaff: boolean, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/leaderboard_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/notification_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/staff_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/staff_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: xsuportal.proto.resources 2 | // file: xsuportal/resources/staff.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | 9 | export class Staff extends jspb.Message { 10 | getId(): number; 11 | setId(value: number): Staff; 12 | 13 | getGithubLogin(): string; 14 | setGithubLogin(value: string): Staff; 15 | 16 | 17 | serializeBinary(): Uint8Array; 18 | toObject(includeInstance?: boolean): Staff.AsObject; 19 | static toObject(includeInstance: boolean, msg: Staff): Staff.AsObject; 20 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 21 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 22 | static serializeBinaryToWriter(message: Staff, writer: jspb.BinaryWriter): void; 23 | static deserializeBinary(bytes: Uint8Array): Staff; 24 | static deserializeBinaryFromReader(message: Staff, reader: jspb.BinaryReader): Staff; 25 | } 26 | 27 | export namespace Staff { 28 | export type AsObject = { 29 | id: number, 30 | githubLogin: string, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/resources/team_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/admin/benchmark_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/admin/clarifications_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/admin/dashboard_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/admin/dashboard_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: xsuportal.proto.services.admin 2 | // file: xsuportal/services/admin/dashboard.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | import * as xsuportal_resources_leaderboard_pb from "../../../xsuportal/resources/leaderboard_pb"; 9 | 10 | export class DashboardRequest extends jspb.Message { 11 | 12 | serializeBinary(): Uint8Array; 13 | toObject(includeInstance?: boolean): DashboardRequest.AsObject; 14 | static toObject(includeInstance: boolean, msg: DashboardRequest): DashboardRequest.AsObject; 15 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 16 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 17 | static serializeBinaryToWriter(message: DashboardRequest, writer: jspb.BinaryWriter): void; 18 | static deserializeBinary(bytes: Uint8Array): DashboardRequest; 19 | static deserializeBinaryFromReader(message: DashboardRequest, reader: jspb.BinaryReader): DashboardRequest; 20 | } 21 | 22 | export namespace DashboardRequest { 23 | export type AsObject = { 24 | } 25 | } 26 | 27 | export class DashboardResponse extends jspb.Message { 28 | 29 | hasLeaderboard(): boolean; 30 | clearLeaderboard(): void; 31 | getLeaderboard(): xsuportal_resources_leaderboard_pb.Leaderboard | undefined; 32 | setLeaderboard(value?: xsuportal_resources_leaderboard_pb.Leaderboard): DashboardResponse; 33 | 34 | 35 | serializeBinary(): Uint8Array; 36 | toObject(includeInstance?: boolean): DashboardResponse.AsObject; 37 | static toObject(includeInstance: boolean, msg: DashboardResponse): DashboardResponse.AsObject; 38 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 39 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 40 | static serializeBinaryToWriter(message: DashboardResponse, writer: jspb.BinaryWriter): void; 41 | static deserializeBinary(bytes: Uint8Array): DashboardResponse; 42 | static deserializeBinaryFromReader(message: DashboardResponse, reader: jspb.BinaryReader): DashboardResponse; 43 | } 44 | 45 | export namespace DashboardResponse { 46 | export type AsObject = { 47 | leaderboard?: xsuportal_resources_leaderboard_pb.Leaderboard.AsObject, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/admin/initialize_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/admin/teams_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/audience/dashboard_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/audience/team_list_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/common/me_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/benchmark_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/clarifications_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/dashboard_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/login_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/login_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: xsuportal.proto.services.contestant 2 | // file: xsuportal/services/contestant/login.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | 9 | export class LoginRequest extends jspb.Message { 10 | getContestantId(): string; 11 | setContestantId(value: string): LoginRequest; 12 | 13 | getPassword(): string; 14 | setPassword(value: string): LoginRequest; 15 | 16 | 17 | serializeBinary(): Uint8Array; 18 | toObject(includeInstance?: boolean): LoginRequest.AsObject; 19 | static toObject(includeInstance: boolean, msg: LoginRequest): LoginRequest.AsObject; 20 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 21 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 22 | static serializeBinaryToWriter(message: LoginRequest, writer: jspb.BinaryWriter): void; 23 | static deserializeBinary(bytes: Uint8Array): LoginRequest; 24 | static deserializeBinaryFromReader(message: LoginRequest, reader: jspb.BinaryReader): LoginRequest; 25 | } 26 | 27 | export namespace LoginRequest { 28 | export type AsObject = { 29 | contestantId: string, 30 | password: string, 31 | } 32 | } 33 | 34 | export class LoginResponse extends jspb.Message { 35 | 36 | serializeBinary(): Uint8Array; 37 | toObject(includeInstance?: boolean): LoginResponse.AsObject; 38 | static toObject(includeInstance: boolean, msg: LoginResponse): LoginResponse.AsObject; 39 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 40 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 41 | static serializeBinaryToWriter(message: LoginResponse, writer: jspb.BinaryWriter): void; 42 | static deserializeBinary(bytes: Uint8Array): LoginResponse; 43 | static deserializeBinaryFromReader(message: LoginResponse, reader: jspb.BinaryReader): LoginResponse; 44 | } 45 | 46 | export namespace LoginResponse { 47 | export type AsObject = { 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/logout_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/logout_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: xsuportal.proto.services.contestant 2 | // file: xsuportal/services/contestant/logout.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | 9 | export class LogoutRequest extends jspb.Message { 10 | 11 | serializeBinary(): Uint8Array; 12 | toObject(includeInstance?: boolean): LogoutRequest.AsObject; 13 | static toObject(includeInstance: boolean, msg: LogoutRequest): LogoutRequest.AsObject; 14 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 15 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 16 | static serializeBinaryToWriter(message: LogoutRequest, writer: jspb.BinaryWriter): void; 17 | static deserializeBinary(bytes: Uint8Array): LogoutRequest; 18 | static deserializeBinaryFromReader(message: LogoutRequest, reader: jspb.BinaryReader): LogoutRequest; 19 | } 20 | 21 | export namespace LogoutRequest { 22 | export type AsObject = { 23 | } 24 | } 25 | 26 | export class LogoutResponse extends jspb.Message { 27 | 28 | serializeBinary(): Uint8Array; 29 | toObject(includeInstance?: boolean): LogoutResponse.AsObject; 30 | static toObject(includeInstance: boolean, msg: LogoutResponse): LogoutResponse.AsObject; 31 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 32 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 33 | static serializeBinaryToWriter(message: LogoutResponse, writer: jspb.BinaryWriter): void; 34 | static deserializeBinary(bytes: Uint8Array): LogoutResponse; 35 | static deserializeBinaryFromReader(message: LogoutResponse, reader: jspb.BinaryReader): LogoutResponse; 36 | } 37 | 38 | export namespace LogoutResponse { 39 | export type AsObject = { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/notifications_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/signup_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/contestant/signup_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: xsuportal.proto.services.contestant 2 | // file: xsuportal/services/contestant/signup.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | 9 | export class SignupRequest extends jspb.Message { 10 | getContestantId(): string; 11 | setContestantId(value: string): SignupRequest; 12 | 13 | getPassword(): string; 14 | setPassword(value: string): SignupRequest; 15 | 16 | 17 | serializeBinary(): Uint8Array; 18 | toObject(includeInstance?: boolean): SignupRequest.AsObject; 19 | static toObject(includeInstance: boolean, msg: SignupRequest): SignupRequest.AsObject; 20 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 21 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 22 | static serializeBinaryToWriter(message: SignupRequest, writer: jspb.BinaryWriter): void; 23 | static deserializeBinary(bytes: Uint8Array): SignupRequest; 24 | static deserializeBinaryFromReader(message: SignupRequest, reader: jspb.BinaryReader): SignupRequest; 25 | } 26 | 27 | export namespace SignupRequest { 28 | export type AsObject = { 29 | contestantId: string, 30 | password: string, 31 | } 32 | } 33 | 34 | export class SignupResponse extends jspb.Message { 35 | 36 | serializeBinary(): Uint8Array; 37 | toObject(includeInstance?: boolean): SignupResponse.AsObject; 38 | static toObject(includeInstance: boolean, msg: SignupResponse): SignupResponse.AsObject; 39 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 40 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 41 | static serializeBinaryToWriter(message: SignupResponse, writer: jspb.BinaryWriter): void; 42 | static deserializeBinary(bytes: Uint8Array): SignupResponse; 43 | static deserializeBinaryFromReader(message: SignupResponse, reader: jspb.BinaryReader): SignupResponse; 44 | } 45 | 46 | export namespace SignupResponse { 47 | export type AsObject = { 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/registration/create_team_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/registration/join_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/proto/xsuportal/services/registration/session_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /webapp/nodejs/public: -------------------------------------------------------------------------------- 1 | ../frontend/public -------------------------------------------------------------------------------- /webapp/nodejs/src/bin/app.ts: -------------------------------------------------------------------------------- 1 | import { app } from "../app"; 2 | 3 | process.on("unhandledRejection", (e) => { 4 | console.error(e); 5 | process.exit(1); 6 | }); 7 | 8 | process.on("uncaughtExcection", (e) => { 9 | console.error(e); 10 | process.exit(1); 11 | }); 12 | 13 | app.listen(process.env.PORT ?? 9292, () => { 14 | console.log("Listening on 9292"); 15 | }); -------------------------------------------------------------------------------- /webapp/ruby/.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | /vendor/bundle/ 3 | -------------------------------------------------------------------------------- /webapp/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'puma' 4 | gem 'sinatra' 5 | gem 'mysql2' 6 | gem 'mysql2-cs-bind' 7 | 8 | # Griffin is a process-model gRPC server implementation. Protocol stack is implemented at grpc_kit gem. 9 | # There are patches to fix critical bugs in grpc_kit gem. Patches will be posted at https://github.com/cookpad/grpc_kit/pulls after contest begins. 10 | gem 'griffin' 11 | gem 'griffin-interceptors' 12 | gem 'grpc_kit', git: 'https://isucon10-public.s3.dualstack.ap-northeast-1.amazonaws.com/git/6MGgQp9lDmsK6G0CNa0DCOWHkOXID9-1davWxzKzI28/grpc_kit.git', ref: 'blocking-recv-buffer' 13 | 14 | gem 'webpush' 15 | 16 | group :development do 17 | gem 'sinatra-contrib' 18 | gem 'grpc-tools' 19 | end 20 | -------------------------------------------------------------------------------- /webapp/ruby/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://isucon10-public.s3.dualstack.ap-northeast-1.amazonaws.com/git/6MGgQp9lDmsK6G0CNa0DCOWHkOXID9-1davWxzKzI28/grpc_kit.git 3 | revision: 371fccb1ae4dd4568dc3788155d93078bf95186c 4 | ref: blocking-recv-buffer 5 | specs: 6 | grpc_kit (0.3.9) 7 | ds9 (>= 1.4.0) 8 | google-protobuf (>= 3.7.0) 9 | googleapis-common-protos-types (>= 1.0.2) 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | backports (3.18.2) 15 | ds9 (1.4.1) 16 | mini_portile2 (>= 2.2.0) 17 | ffi (1.13.1) 18 | get_process_mem (0.2.7) 19 | ffi (~> 1.0) 20 | google-protobuf (3.13.0) 21 | googleapis-common-protos-types (1.0.5) 22 | google-protobuf (~> 3.11) 23 | griffin (0.2.4) 24 | grpc_kit (>= 0.3.8) 25 | serverengine (~> 2.0.7) 26 | griffin-interceptors (0.1.9) 27 | get_process_mem (~> 0.2.3) 28 | griffin (>= 0.2.3) 29 | grpc-tools (1.31.1) 30 | hkdf (0.3.0) 31 | jwt (2.2.2) 32 | mini_portile2 (2.5.0) 33 | multi_json (1.15.0) 34 | mustermann (1.1.1) 35 | ruby2_keywords (~> 0.0.1) 36 | mysql2 (0.5.3) 37 | mysql2-cs-bind (0.0.7) 38 | mysql2 39 | nio4r (2.5.2) 40 | puma (4.3.5) 41 | nio4r (~> 2.0) 42 | rack (2.2.3) 43 | rack-protection (2.0.8.1) 44 | rack 45 | ruby2_keywords (0.0.2) 46 | serverengine (2.0.7) 47 | sigdump (~> 0.2.2) 48 | sigdump (0.2.4) 49 | sinatra (2.0.8.1) 50 | mustermann (~> 1.0) 51 | rack (~> 2.0) 52 | rack-protection (= 2.0.8.1) 53 | tilt (~> 2.0) 54 | sinatra-contrib (2.0.8.1) 55 | backports (>= 2.8.2) 56 | multi_json 57 | mustermann (~> 1.0) 58 | rack-protection (= 2.0.8.1) 59 | sinatra (= 2.0.8.1) 60 | tilt (~> 2.0) 61 | tilt (2.0.10) 62 | webpush (1.0.0) 63 | hkdf (~> 0.2) 64 | jwt (~> 2.0) 65 | 66 | PLATFORMS 67 | ruby 68 | 69 | DEPENDENCIES 70 | griffin 71 | griffin-interceptors 72 | grpc-tools 73 | grpc_kit! 74 | mysql2 75 | mysql2-cs-bind 76 | puma 77 | sinatra 78 | sinatra-contrib 79 | webpush 80 | 81 | BUNDLED WITH 82 | 2.1.4 83 | -------------------------------------------------------------------------------- /webapp/ruby/bin/benchmark_server.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $: << File.expand_path('../lib', __dir__) 4 | require 'etc' 5 | require 'logger' 6 | require 'griffin' 7 | require 'grpc' 8 | require 'griffin/interceptors/server/logging_interceptor' 9 | require_relative '../grpc/benchmark_queue_service' 10 | require_relative '../grpc/benchmark_report_service' 11 | 12 | Griffin::Server.configure do |c| 13 | c.bind '0.0.0.0' 14 | 15 | c.services [ 16 | BenchmarkQueueService, 17 | BenchmarkReportService, 18 | ] 19 | 20 | c.interceptors [ 21 | Griffin::Interceptors::Server::LoggingInterceptor.new, 22 | ] 23 | 24 | # Number of processes 25 | c.workers 1 26 | # Min/Max number of threads per process to handle gRPC call (= maximum concurrent number of gRPC requests per process) 27 | c.pool_size 15,15 28 | # Min/Max number of threads per process to handle HTTP/2 connection (= maximum concurrent connection per process) 29 | c.connection_size 2,2 30 | 31 | c.logger Logger.new($stdout) 32 | end 33 | 34 | Griffin::Server.run(port: ENV.fetch('PORT', '50051')&.to_i) 35 | -------------------------------------------------------------------------------- /webapp/ruby/config.ru: -------------------------------------------------------------------------------- 1 | $: << File.expand_path('lib', __dir__) 2 | require 'sinatra' 3 | require './app.rb' 4 | 5 | run Xsuportal::App 6 | -------------------------------------------------------------------------------- /webapp/ruby/config/puma.rb: -------------------------------------------------------------------------------- 1 | bind 'tcp://0.0.0.0:9292' 2 | preload_app! 3 | workers 2 4 | threads 32,32 5 | log_requests true 6 | -------------------------------------------------------------------------------- /webapp/ruby/lib/google/protobuf/timestamp_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: google/protobuf/timestamp.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("google/protobuf/timestamp.proto", :syntax => :proto3) do 8 | add_message "google.protobuf.Timestamp" do 9 | optional :seconds, :int64, 1 10 | optional :nanos, :int32, 2 11 | end 12 | end 13 | end 14 | 15 | module Google 16 | module Protobuf 17 | Timestamp = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("google.protobuf.Timestamp").msgclass 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/error_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/error.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("xsuportal/error.proto", :syntax => :proto3) do 8 | add_message "xsuportal.proto.Error" do 9 | optional :code, :int32, 1 10 | optional :name, :string, 2 11 | optional :human_message, :string, 3 12 | repeated :human_descriptions, :string, 4 13 | optional :debug_info, :message, 16, "xsuportal.proto.Error.DebugInfo" 14 | end 15 | add_message "xsuportal.proto.Error.DebugInfo" do 16 | optional :exception, :string, 1 17 | repeated :trace, :string, 2 18 | repeated :application_trace, :string, 3 19 | repeated :framework_trace, :string, 4 20 | end 21 | end 22 | end 23 | 24 | module Xsuportal 25 | module Proto 26 | Error = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.Error").msgclass 27 | Error::DebugInfo = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.Error.DebugInfo").msgclass 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/resources/benchmark_job_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/resources/benchmark_job.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/benchmark_result_pb' 7 | require 'xsuportal/resources/team_pb' 8 | require 'google/protobuf/timestamp_pb' 9 | Google::Protobuf::DescriptorPool.generated_pool.build do 10 | add_file("xsuportal/resources/benchmark_job.proto", :syntax => :proto3) do 11 | add_message "xsuportal.proto.resources.BenchmarkJob" do 12 | optional :id, :int64, 1 13 | optional :team_id, :int64, 2 14 | optional :status, :enum, 4, "xsuportal.proto.resources.BenchmarkJob.Status" 15 | optional :created_at, :message, 5, "google.protobuf.Timestamp" 16 | optional :updated_at, :message, 6, "google.protobuf.Timestamp" 17 | optional :started_at, :message, 7, "google.protobuf.Timestamp" 18 | optional :finished_at, :message, 8, "google.protobuf.Timestamp" 19 | optional :team, :message, 16, "xsuportal.proto.resources.Team" 20 | optional :result, :message, 18, "xsuportal.proto.resources.BenchmarkResult" 21 | optional :target_hostname, :string, 30 22 | end 23 | add_enum "xsuportal.proto.resources.BenchmarkJob.Status" do 24 | value :PENDING, 0 25 | value :SENT, 1 26 | value :RUNNING, 2 27 | value :ERRORED, 3 28 | value :CANCELLED, 4 29 | value :FINISHED, 5 30 | end 31 | end 32 | end 33 | 34 | module Xsuportal 35 | module Proto 36 | module Resources 37 | BenchmarkJob = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.BenchmarkJob").msgclass 38 | BenchmarkJob::Status = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.BenchmarkJob.Status").enummodule 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/resources/benchmark_result_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/resources/benchmark_result.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'google/protobuf/timestamp_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/resources/benchmark_result.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.resources.BenchmarkResult" do 10 | optional :finished, :bool, 1 11 | optional :passed, :bool, 2 12 | optional :score, :int64, 3 13 | optional :score_breakdown, :message, 4, "xsuportal.proto.resources.BenchmarkResult.ScoreBreakdown" 14 | optional :reason, :string, 5 15 | optional :marked_at, :message, 6, "google.protobuf.Timestamp" 16 | end 17 | add_message "xsuportal.proto.resources.BenchmarkResult.ScoreBreakdown" do 18 | optional :raw, :int64, 1 19 | optional :deduction, :int64, 2 20 | end 21 | end 22 | end 23 | 24 | module Xsuportal 25 | module Proto 26 | module Resources 27 | BenchmarkResult = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.BenchmarkResult").msgclass 28 | BenchmarkResult::ScoreBreakdown = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.BenchmarkResult.ScoreBreakdown").msgclass 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/resources/clarification_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/resources/clarification.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/team_pb' 7 | require 'google/protobuf/timestamp_pb' 8 | Google::Protobuf::DescriptorPool.generated_pool.build do 9 | add_file("xsuportal/resources/clarification.proto", :syntax => :proto3) do 10 | add_message "xsuportal.proto.resources.Clarification" do 11 | optional :id, :int64, 1 12 | optional :team_id, :int64, 2 13 | optional :answered, :bool, 3 14 | optional :disclosed, :bool, 4 15 | optional :question, :string, 5 16 | optional :answer, :string, 6 17 | optional :created_at, :message, 7, "google.protobuf.Timestamp" 18 | optional :answered_at, :message, 8, "google.protobuf.Timestamp" 19 | optional :team, :message, 16, "xsuportal.proto.resources.Team" 20 | end 21 | end 22 | end 23 | 24 | module Xsuportal 25 | module Proto 26 | module Resources 27 | Clarification = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Clarification").msgclass 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/resources/contest_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/resources/contest.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'google/protobuf/timestamp_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/resources/contest.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.resources.Contest" do 10 | optional :registration_open_at, :message, 1, "google.protobuf.Timestamp" 11 | optional :contest_starts_at, :message, 3, "google.protobuf.Timestamp" 12 | optional :contest_freezes_at, :message, 4, "google.protobuf.Timestamp" 13 | optional :contest_ends_at, :message, 5, "google.protobuf.Timestamp" 14 | optional :status, :enum, 6, "xsuportal.proto.resources.Contest.Status" 15 | optional :frozen, :bool, 7 16 | end 17 | add_enum "xsuportal.proto.resources.Contest.Status" do 18 | value :STANDBY, 0 19 | value :REGISTRATION, 1 20 | value :STARTED, 2 21 | value :FINISHED, 3 22 | end 23 | end 24 | end 25 | 26 | module Xsuportal 27 | module Proto 28 | module Resources 29 | Contest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Contest").msgclass 30 | Contest::Status = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Contest.Status").enummodule 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/resources/contestant_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/resources/contestant.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("xsuportal/resources/contestant.proto", :syntax => :proto3) do 8 | add_message "xsuportal.proto.resources.Contestant" do 9 | optional :id, :string, 1 10 | optional :team_id, :int64, 2 11 | optional :name, :string, 3 12 | optional :is_student, :bool, 4 13 | optional :is_staff, :bool, 5 14 | end 15 | end 16 | end 17 | 18 | module Xsuportal 19 | module Proto 20 | module Resources 21 | Contestant = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Contestant").msgclass 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/resources/notification_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/resources/notification.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'google/protobuf/timestamp_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/resources/notification.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.resources.Notification" do 10 | optional :id, :int64, 1 11 | optional :created_at, :message, 2, "google.protobuf.Timestamp" 12 | oneof :content do 13 | optional :content_benchmark_job, :message, 3, "xsuportal.proto.resources.Notification.BenchmarkJobMessage" 14 | optional :content_clarification, :message, 4, "xsuportal.proto.resources.Notification.ClarificationMessage" 15 | optional :content_test, :message, 5, "xsuportal.proto.resources.Notification.TestMessage" 16 | end 17 | end 18 | add_message "xsuportal.proto.resources.Notification.BenchmarkJobMessage" do 19 | optional :benchmark_job_id, :int64, 1 20 | end 21 | add_message "xsuportal.proto.resources.Notification.ClarificationMessage" do 22 | optional :clarification_id, :int64, 1 23 | optional :owned, :bool, 2 24 | optional :updated, :bool, 3 25 | end 26 | add_message "xsuportal.proto.resources.Notification.TestMessage" do 27 | optional :something, :int64, 1 28 | end 29 | end 30 | end 31 | 32 | module Xsuportal 33 | module Proto 34 | module Resources 35 | Notification = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Notification").msgclass 36 | Notification::BenchmarkJobMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Notification.BenchmarkJobMessage").msgclass 37 | Notification::ClarificationMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Notification.ClarificationMessage").msgclass 38 | Notification::TestMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Notification.TestMessage").msgclass 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/resources/staff_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/resources/staff.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("xsuportal/resources/staff.proto", :syntax => :proto3) do 8 | add_message "xsuportal.proto.resources.Staff" do 9 | optional :id, :int64, 1 10 | optional :github_login, :string, 2 11 | end 12 | end 13 | end 14 | 15 | module Xsuportal 16 | module Proto 17 | module Resources 18 | Staff = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Staff").msgclass 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/resources/team_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/resources/team.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/contestant_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/resources/team.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.resources.Team" do 10 | optional :id, :int64, 1 11 | optional :name, :string, 2 12 | optional :leader_id, :string, 3 13 | repeated :member_ids, :string, 4 14 | optional :withdrawn, :bool, 7 15 | optional :student, :message, 10, "xsuportal.proto.resources.Team.StudentStatus" 16 | optional :detail, :message, 8, "xsuportal.proto.resources.Team.TeamDetail" 17 | optional :leader, :message, 16, "xsuportal.proto.resources.Contestant" 18 | repeated :members, :message, 17, "xsuportal.proto.resources.Contestant" 19 | end 20 | add_message "xsuportal.proto.resources.Team.StudentStatus" do 21 | optional :status, :bool, 1 22 | end 23 | add_message "xsuportal.proto.resources.Team.TeamDetail" do 24 | optional :email_address, :string, 1 25 | optional :invite_token, :string, 16 26 | end 27 | end 28 | end 29 | 30 | module Xsuportal 31 | module Proto 32 | module Resources 33 | Team = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Team").msgclass 34 | Team::StudentStatus = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Team.StudentStatus").msgclass 35 | Team::TeamDetail = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.resources.Team.TeamDetail").msgclass 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/admin/dashboard_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/admin/dashboard.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/leaderboard_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/services/admin/dashboard.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.services.admin.DashboardRequest" do 10 | end 11 | add_message "xsuportal.proto.services.admin.DashboardResponse" do 12 | optional :leaderboard, :message, 1, "xsuportal.proto.resources.Leaderboard" 13 | end 14 | end 15 | end 16 | 17 | module Xsuportal 18 | module Proto 19 | module Services 20 | module Admin 21 | DashboardRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.admin.DashboardRequest").msgclass 22 | DashboardResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.admin.DashboardResponse").msgclass 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/admin/initialize_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/admin/initialize.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/contest_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/services/admin/initialize.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.services.admin.InitializeRequest" do 10 | optional :contest, :message, 1, "xsuportal.proto.resources.Contest" 11 | end 12 | add_message "xsuportal.proto.services.admin.InitializeResponse" do 13 | optional :language, :string, 1 14 | optional :benchmark_server, :message, 2, "xsuportal.proto.services.admin.InitializeResponse.BenchmarkServer" 15 | end 16 | add_message "xsuportal.proto.services.admin.InitializeResponse.BenchmarkServer" do 17 | optional :host, :string, 1 18 | optional :port, :int64, 2 19 | end 20 | end 21 | end 22 | 23 | module Xsuportal 24 | module Proto 25 | module Services 26 | module Admin 27 | InitializeRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.admin.InitializeRequest").msgclass 28 | InitializeResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.admin.InitializeResponse").msgclass 29 | InitializeResponse::BenchmarkServer = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.admin.InitializeResponse.BenchmarkServer").msgclass 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/audience/dashboard_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/audience/dashboard.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/leaderboard_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/services/audience/dashboard.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.services.audience.DashboardRequest" do 10 | end 11 | add_message "xsuportal.proto.services.audience.DashboardResponse" do 12 | optional :leaderboard, :message, 1, "xsuportal.proto.resources.Leaderboard" 13 | end 14 | end 15 | end 16 | 17 | module Xsuportal 18 | module Proto 19 | module Services 20 | module Audience 21 | DashboardRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.audience.DashboardRequest").msgclass 22 | DashboardResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.audience.DashboardResponse").msgclass 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/audience/team_list_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/audience/team_list.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("xsuportal/services/audience/team_list.proto", :syntax => :proto3) do 8 | add_message "xsuportal.proto.services.audience.ListTeamsResponse" do 9 | repeated :teams, :message, 1, "xsuportal.proto.services.audience.ListTeamsResponse.TeamListItem" 10 | end 11 | add_message "xsuportal.proto.services.audience.ListTeamsResponse.TeamListItem" do 12 | optional :team_id, :int64, 1 13 | optional :name, :string, 2 14 | repeated :member_names, :string, 3 15 | optional :is_student, :bool, 5 16 | end 17 | end 18 | end 19 | 20 | module Xsuportal 21 | module Proto 22 | module Services 23 | module Audience 24 | ListTeamsResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.audience.ListTeamsResponse").msgclass 25 | ListTeamsResponse::TeamListItem = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.audience.ListTeamsResponse.TeamListItem").msgclass 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/bench/receiving_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/bench/receiving.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'google/protobuf/timestamp_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/services/bench/receiving.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.services.bench.ReceiveBenchmarkJobRequest" do 10 | optional :team_id, :int64, 3 11 | end 12 | add_message "xsuportal.proto.services.bench.ReceiveBenchmarkJobResponse" do 13 | optional :job_handle, :message, 1, "xsuportal.proto.services.bench.ReceiveBenchmarkJobResponse.JobHandle" 14 | end 15 | add_message "xsuportal.proto.services.bench.ReceiveBenchmarkJobResponse.JobHandle" do 16 | optional :job_id, :int64, 1 17 | optional :handle, :string, 2 18 | optional :target_hostname, :string, 3 19 | optional :contest_started_at, :message, 10, "google.protobuf.Timestamp" 20 | optional :job_created_at, :message, 11, "google.protobuf.Timestamp" 21 | end 22 | end 23 | end 24 | 25 | module Xsuportal 26 | module Proto 27 | module Services 28 | module Bench 29 | ReceiveBenchmarkJobRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.bench.ReceiveBenchmarkJobRequest").msgclass 30 | ReceiveBenchmarkJobResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.bench.ReceiveBenchmarkJobResponse").msgclass 31 | ReceiveBenchmarkJobResponse::JobHandle = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.bench.ReceiveBenchmarkJobResponse.JobHandle").msgclass 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/bench/receiving_services_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # Source: xsuportal/services/bench/receiving.proto for package 'xsuportal.proto.services.bench' 3 | 4 | require 'grpc' 5 | require 'xsuportal/services/bench/receiving_pb' 6 | 7 | module Xsuportal 8 | module Proto 9 | module Services 10 | module Bench 11 | module BenchmarkQueue 12 | class Service 13 | 14 | include GRPC::GenericService 15 | 16 | self.marshal_class_method = :encode 17 | self.unmarshal_class_method = :decode 18 | self.service_name = 'xsuportal.proto.services.bench.BenchmarkQueue' 19 | 20 | rpc :ReceiveBenchmarkJob, ::Xsuportal::Proto::Services::Bench::ReceiveBenchmarkJobRequest, ::Xsuportal::Proto::Services::Bench::ReceiveBenchmarkJobResponse 21 | end 22 | 23 | Stub = Service.rpc_stub_class 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/bench/reporting_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/bench/reporting.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/benchmark_result_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/services/bench/reporting.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.services.bench.ReportBenchmarkResultRequest" do 10 | optional :job_id, :int64, 1 11 | optional :handle, :string, 2 12 | optional :nonce, :int64, 3 13 | optional :result, :message, 4, "xsuportal.proto.resources.BenchmarkResult" 14 | end 15 | add_message "xsuportal.proto.services.bench.ReportBenchmarkResultResponse" do 16 | optional :acked_nonce, :int64, 1 17 | end 18 | end 19 | end 20 | 21 | module Xsuportal 22 | module Proto 23 | module Services 24 | module Bench 25 | ReportBenchmarkResultRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.bench.ReportBenchmarkResultRequest").msgclass 26 | ReportBenchmarkResultResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.bench.ReportBenchmarkResultResponse").msgclass 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/bench/reporting_services_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # Source: xsuportal/services/bench/reporting.proto for package 'xsuportal.proto.services.bench' 3 | 4 | require 'grpc' 5 | require 'xsuportal/services/bench/reporting_pb' 6 | 7 | module Xsuportal 8 | module Proto 9 | module Services 10 | module Bench 11 | module BenchmarkReport 12 | class Service 13 | 14 | include GRPC::GenericService 15 | 16 | self.marshal_class_method = :encode 17 | self.unmarshal_class_method = :decode 18 | self.service_name = 'xsuportal.proto.services.bench.BenchmarkReport' 19 | 20 | rpc :ReportBenchmarkResult, stream(::Xsuportal::Proto::Services::Bench::ReportBenchmarkResultRequest), stream(::Xsuportal::Proto::Services::Bench::ReportBenchmarkResultResponse) 21 | end 22 | 23 | Stub = Service.rpc_stub_class 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/common/me_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/common/me.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/team_pb' 7 | require 'xsuportal/resources/contestant_pb' 8 | require 'xsuportal/resources/contest_pb' 9 | Google::Protobuf::DescriptorPool.generated_pool.build do 10 | add_file("xsuportal/services/common/me.proto", :syntax => :proto3) do 11 | add_message "xsuportal.proto.services.common.GetCurrentSessionResponse" do 12 | optional :team, :message, 1, "xsuportal.proto.resources.Team" 13 | optional :contestant, :message, 2, "xsuportal.proto.resources.Contestant" 14 | optional :contest, :message, 4, "xsuportal.proto.resources.Contest" 15 | optional :push_vapid_key, :string, 6 16 | end 17 | end 18 | end 19 | 20 | module Xsuportal 21 | module Proto 22 | module Services 23 | module Common 24 | GetCurrentSessionResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.common.GetCurrentSessionResponse").msgclass 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/contestant/clarifications_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/contestant/clarifications.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/clarification_pb' 7 | Google::Protobuf::DescriptorPool.generated_pool.build do 8 | add_file("xsuportal/services/contestant/clarifications.proto", :syntax => :proto3) do 9 | add_message "xsuportal.proto.services.contestant.ListClarificationsRequest" do 10 | end 11 | add_message "xsuportal.proto.services.contestant.ListClarificationsResponse" do 12 | repeated :clarifications, :message, 1, "xsuportal.proto.resources.Clarification" 13 | end 14 | add_message "xsuportal.proto.services.contestant.RequestClarificationRequest" do 15 | optional :question, :string, 1 16 | end 17 | add_message "xsuportal.proto.services.contestant.RequestClarificationResponse" do 18 | optional :clarification, :message, 1, "xsuportal.proto.resources.Clarification" 19 | end 20 | end 21 | end 22 | 23 | module Xsuportal 24 | module Proto 25 | module Services 26 | module Contestant 27 | ListClarificationsRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.ListClarificationsRequest").msgclass 28 | ListClarificationsResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.ListClarificationsResponse").msgclass 29 | RequestClarificationRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.RequestClarificationRequest").msgclass 30 | RequestClarificationResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.RequestClarificationResponse").msgclass 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/contestant/dashboard_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/contestant/dashboard.proto 3 | 4 | require 'google/protobuf' 5 | 6 | require 'xsuportal/resources/leaderboard_pb' 7 | require 'xsuportal/resources/benchmark_job_pb' 8 | Google::Protobuf::DescriptorPool.generated_pool.build do 9 | add_file("xsuportal/services/contestant/dashboard.proto", :syntax => :proto3) do 10 | add_message "xsuportal.proto.services.contestant.DashboardRequest" do 11 | end 12 | add_message "xsuportal.proto.services.contestant.DashboardResponse" do 13 | optional :leaderboard, :message, 1, "xsuportal.proto.resources.Leaderboard" 14 | end 15 | end 16 | end 17 | 18 | module Xsuportal 19 | module Proto 20 | module Services 21 | module Contestant 22 | DashboardRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.DashboardRequest").msgclass 23 | DashboardResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.DashboardResponse").msgclass 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/contestant/login_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/contestant/login.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("xsuportal/services/contestant/login.proto", :syntax => :proto3) do 8 | add_message "xsuportal.proto.services.contestant.LoginRequest" do 9 | optional :contestant_id, :string, 1 10 | optional :password, :string, 2 11 | end 12 | add_message "xsuportal.proto.services.contestant.LoginResponse" do 13 | end 14 | end 15 | end 16 | 17 | module Xsuportal 18 | module Proto 19 | module Services 20 | module Contestant 21 | LoginRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.LoginRequest").msgclass 22 | LoginResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.LoginResponse").msgclass 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/contestant/logout_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/contestant/logout.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("xsuportal/services/contestant/logout.proto", :syntax => :proto3) do 8 | add_message "xsuportal.proto.services.contestant.LogoutRequest" do 9 | end 10 | add_message "xsuportal.proto.services.contestant.LogoutResponse" do 11 | end 12 | end 13 | end 14 | 15 | module Xsuportal 16 | module Proto 17 | module Services 18 | module Contestant 19 | LogoutRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.LogoutRequest").msgclass 20 | LogoutResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.LogoutResponse").msgclass 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/contestant/signup_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/contestant/signup.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("xsuportal/services/contestant/signup.proto", :syntax => :proto3) do 8 | add_message "xsuportal.proto.services.contestant.SignupRequest" do 9 | optional :contestant_id, :string, 1 10 | optional :password, :string, 2 11 | end 12 | add_message "xsuportal.proto.services.contestant.SignupResponse" do 13 | end 14 | end 15 | end 16 | 17 | module Xsuportal 18 | module Proto 19 | module Services 20 | module Contestant 21 | SignupRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.SignupRequest").msgclass 22 | SignupResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.contestant.SignupResponse").msgclass 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/registration/create_team_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/registration/create_team.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("xsuportal/services/registration/create_team.proto", :syntax => :proto3) do 8 | add_message "xsuportal.proto.services.registration.CreateTeamRequest" do 9 | optional :team_name, :string, 1 10 | optional :name, :string, 2 11 | optional :email_address, :string, 3 12 | optional :is_student, :bool, 4 13 | end 14 | add_message "xsuportal.proto.services.registration.CreateTeamResponse" do 15 | optional :team_id, :int64, 1 16 | end 17 | end 18 | end 19 | 20 | module Xsuportal 21 | module Proto 22 | module Services 23 | module Registration 24 | CreateTeamRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.registration.CreateTeamRequest").msgclass 25 | CreateTeamResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.registration.CreateTeamResponse").msgclass 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /webapp/ruby/lib/xsuportal/services/registration/join_pb.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: xsuportal/services/registration/join.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_file("xsuportal/services/registration/join.proto", :syntax => :proto3) do 8 | add_message "xsuportal.proto.services.registration.JoinTeamRequest" do 9 | optional :team_id, :int64, 1 10 | optional :invite_token, :string, 2 11 | optional :name, :string, 3 12 | optional :is_student, :bool, 4 13 | end 14 | add_message "xsuportal.proto.services.registration.JoinTeamResponse" do 15 | end 16 | end 17 | end 18 | 19 | module Xsuportal 20 | module Proto 21 | module Services 22 | module Registration 23 | JoinTeamRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.registration.JoinTeamRequest").msgclass 24 | JoinTeamResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("xsuportal.proto.services.registration.JoinTeamResponse").msgclass 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /webapp/ruby/public: -------------------------------------------------------------------------------- 1 | ../frontend/public -------------------------------------------------------------------------------- /webapp/rust/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /webapp/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xsuportal" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [build-dependencies] 9 | tonic-build = "0.3" 10 | 11 | [dependencies] 12 | actix-files = "0.3" 13 | actix-protobuf = "0.6" 14 | actix-session = "0.4" 15 | actix-web = "3.0" 16 | async-stream = "0.2" 17 | base64 = "0.12" 18 | byteorder = "1.3" 19 | bytes = "0.5" 20 | chrono = "0.4" 21 | data-encoding = "2.3" 22 | env_logger = "0.7" 23 | futures = "0.3" 24 | jsonwebtoken = "7.2" 25 | lazy_static = "1.4" 26 | listenfd = "0.3" 27 | log = "0.4" 28 | mime = "0.3" 29 | mysql = "18.2" 30 | openssl = "0.10" 31 | prost = "0.6" 32 | prost-types = "0.6" 33 | r2d2 = "0.8" 34 | r2d2_mysql = "18.0" 35 | rand = "0.7" 36 | reqwest = "0.10" 37 | ring = "0.16" 38 | serde = "1.0" 39 | serde_json = "1.0" 40 | structopt = "0.3" 41 | tokio = { version = "0.2", features = ["full"] } 42 | tonic = "0.3" 43 | url = "2.1" 44 | -------------------------------------------------------------------------------- /webapp/rust/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), Box> { 2 | tonic_build::configure().build_client(false).compile( 3 | &[ 4 | "../../proto/xsuportal/error.proto", 5 | "../../proto/xsuportal/resources/benchmark_job.proto", 6 | "../../proto/xsuportal/resources/benchmark_result.proto", 7 | "../../proto/xsuportal/resources/clarification.proto", 8 | "../../proto/xsuportal/resources/contest.proto", 9 | "../../proto/xsuportal/resources/contestant.proto", 10 | "../../proto/xsuportal/resources/leaderboard.proto", 11 | "../../proto/xsuportal/resources/team.proto", 12 | "../../proto/xsuportal/services/admin/clarifications.proto", 13 | "../../proto/xsuportal/services/admin/initialize.proto", 14 | "../../proto/xsuportal/services/audience/dashboard.proto", 15 | "../../proto/xsuportal/services/audience/team_list.proto", 16 | "../../proto/xsuportal/services/bench/receiving.proto", 17 | "../../proto/xsuportal/services/bench/reporting.proto", 18 | "../../proto/xsuportal/services/common/me.proto", 19 | "../../proto/xsuportal/services/contestant/benchmark.proto", 20 | "../../proto/xsuportal/services/contestant/clarifications.proto", 21 | "../../proto/xsuportal/services/contestant/dashboard.proto", 22 | "../../proto/xsuportal/services/contestant/login.proto", 23 | "../../proto/xsuportal/services/contestant/logout.proto", 24 | "../../proto/xsuportal/services/contestant/notifications.proto", 25 | "../../proto/xsuportal/services/contestant/signup.proto", 26 | "../../proto/xsuportal/services/registration/create_team.proto", 27 | "../../proto/xsuportal/services/registration/join.proto", 28 | "../../proto/xsuportal/services/registration/session.proto", 29 | ], 30 | &["../../proto"], 31 | )?; 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /webapp/rust/public: -------------------------------------------------------------------------------- 1 | ../frontend/public -------------------------------------------------------------------------------- /webapp/rust/src/audience.rs: -------------------------------------------------------------------------------- 1 | use crate::proto::services::audience::{ 2 | list_teams_response::TeamListItem, DashboardResponse, ListTeamsResponse, 3 | }; 4 | use actix_protobuf::ProtoBufResponseBuilder; 5 | use actix_web::{web, Error as AWError, HttpResponse}; 6 | use mysql::prelude::*; 7 | 8 | pub async fn list_teams(db: web::Data) -> Result { 9 | let items = web::block::<_, _, mysql::Error>(move || { 10 | let mut conn = db.get().expect("Failed to checkout database connection"); 11 | let teams: Vec = conn 12 | .query("SELECT * FROM `teams` WHERE `withdrawn` = FALSE ORDER BY `created_at` DESC")?; 13 | let mut items = Vec::new(); 14 | for team in teams { 15 | let members: Vec = conn.exec( 16 | "SELECT * FROM `contestants` WHERE `team_id` = ? ORDER BY `created_at`", 17 | (team.id,), 18 | )?; 19 | items.push(TeamListItem { 20 | team_id: team.id, 21 | name: team.name, 22 | is_student: members.iter().all(|c| c.student), 23 | member_names: members 24 | .into_iter() 25 | .map(|c| c.name.unwrap_or_else(|| "".to_owned())) 26 | .collect(), 27 | }); 28 | } 29 | Ok(items) 30 | }) 31 | .await?; 32 | HttpResponse::Ok().protobuf(ListTeamsResponse { teams: items }) 33 | } 34 | 35 | pub async fn dashboard(db: web::Data) -> Result { 36 | let leaderboard = web::block(move || { 37 | let mut conn = db.get().expect("Failed to checkout database connection"); 38 | crate::leaderboard::get_leaderboard(&mut conn, 0) 39 | }) 40 | .await?; 41 | HttpResponse::Ok().protobuf(DashboardResponse { 42 | leaderboard: Some(leaderboard), 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /webapp/rust/src/bin/benchmark_server.rs: -------------------------------------------------------------------------------- 1 | use listenfd::ListenFd; 2 | use std::env; 3 | use std::sync::Arc; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Box> { 7 | if env::var("RUST_LOG").is_err() { 8 | env::set_var("RUST_LOG", "xsuportal=info"); 9 | } 10 | env_logger::init(); 11 | 12 | let mysql_connection_env = xsuportal::MySQLConnectionEnv::default(); 13 | let manager = r2d2_mysql::MysqlConnectionManager::new( 14 | mysql::OptsBuilder::new() 15 | .ip_or_hostname(Some(&mysql_connection_env.host)) 16 | .tcp_port(mysql_connection_env.port) 17 | .user(Some(&mysql_connection_env.user)) 18 | .db_name(Some(&mysql_connection_env.db_name)) 19 | .pass(Some(&mysql_connection_env.password)) 20 | .init(vec!["SET time_zone='+00:00'".to_owned()]), 21 | ); 22 | let pool = Arc::new( 23 | r2d2::Pool::builder() 24 | .max_size(10) 25 | .build(manager) 26 | .expect("Failed to create connection pool"), 27 | ); 28 | 29 | let server = tonic::transport::Server::builder() 30 | .add_service( 31 | xsuportal::proto::services::bench::benchmark_queue_server::BenchmarkQueueServer::new( 32 | xsuportal::bench::QueueService { db: pool.clone() }, 33 | ), 34 | ) 35 | .add_service( 36 | xsuportal::proto::services::bench::benchmark_report_server::BenchmarkReportServer::new( 37 | xsuportal::bench::ReportService { db: pool }, 38 | ), 39 | ); 40 | 41 | let mut listenfd = ListenFd::from_env(); 42 | if let Some(l) = listenfd.take_tcp_listener(0)? { 43 | let mut listener = tokio::net::TcpListener::from_std(l)?; 44 | server.serve_with_incoming(listener.incoming()).await?; 45 | } else { 46 | server.serve("0.0.0.0:50051".parse().unwrap()).await?; 47 | } 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /webapp/rust/src/proto.rs: -------------------------------------------------------------------------------- 1 | tonic::include_proto!("xsuportal.proto"); 2 | 3 | pub mod resources { 4 | tonic::include_proto!("xsuportal.proto.resources"); 5 | } 6 | 7 | pub mod services { 8 | pub mod admin { 9 | tonic::include_proto!("xsuportal.proto.services.admin"); 10 | } 11 | pub mod audience { 12 | tonic::include_proto!("xsuportal.proto.services.audience"); 13 | } 14 | pub mod bench { 15 | tonic::include_proto!("xsuportal.proto.services.bench"); 16 | } 17 | pub mod common { 18 | tonic::include_proto!("xsuportal.proto.services.common"); 19 | } 20 | pub mod contestant { 21 | tonic::include_proto!("xsuportal.proto.services.contestant"); 22 | } 23 | pub mod registration { 24 | tonic::include_proto!("xsuportal.proto.services.registration"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webapp/sql/setup.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `xsuportal` DEFAULT CHARACTER SET utf8mb4; 2 | CREATE USER IF NOT EXISTS `isucon`@`localhost` IDENTIFIED WITH mysql_native_password BY 'isucon'; 3 | GRANT ALL ON `xsuportal`.* TO `isucon`@`localhost`; 4 | -------------------------------------------------------------------------------- /webapp/tools/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'mysql2' 4 | gem 'mysql2-cs-bind' 5 | gem 'grpc_kit', git: 'https://isucon10-public.s3.dualstack.ap-northeast-1.amazonaws.com/git/6MGgQp9lDmsK6G0CNa0DCOWHkOXID9-1davWxzKzI28/grpc_kit.git', ref: 'blocking-recv-buffer' 6 | gem 'webpush' 7 | -------------------------------------------------------------------------------- /webapp/tools/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://isucon10-public.s3.dualstack.ap-northeast-1.amazonaws.com/git/6MGgQp9lDmsK6G0CNa0DCOWHkOXID9-1davWxzKzI28/grpc_kit.git 3 | revision: 371fccb1ae4dd4568dc3788155d93078bf95186c 4 | ref: blocking-recv-buffer 5 | specs: 6 | grpc_kit (0.3.9) 7 | ds9 (>= 1.4.0) 8 | google-protobuf (>= 3.7.0) 9 | googleapis-common-protos-types (>= 1.0.2) 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | ds9 (1.4.1) 15 | mini_portile2 (>= 2.2.0) 16 | google-protobuf (3.13.0) 17 | googleapis-common-protos-types (1.0.5) 18 | google-protobuf (~> 3.11) 19 | hkdf (0.3.0) 20 | jwt (2.2.2) 21 | mini_portile2 (2.5.0) 22 | mysql2 (0.5.3) 23 | mysql2-cs-bind (0.1.0) 24 | mysql2 25 | webpush (1.0.0) 26 | hkdf (~> 0.2) 27 | jwt (~> 2.0) 28 | 29 | PLATFORMS 30 | ruby 31 | 32 | DEPENDENCIES 33 | grpc_kit! 34 | mysql2 35 | mysql2-cs-bind 36 | webpush 37 | 38 | BUNDLED WITH 39 | 2.1.4 40 | -------------------------------------------------------------------------------- /webapp/tools/add_benchmark_job: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'optparse' 3 | require 'mysql2' 4 | require 'mysql2-cs-bind' 5 | 6 | def db 7 | @db ||= Mysql2::Client.new( 8 | host: ENV['MYSQL_HOSTNAME'] || '127.0.0.1', 9 | port: ENV['MYSQL_PORT'] || '3306', 10 | username: ENV['MYSQL_USER'] || 'isucon', 11 | database: ENV['MYSQL_DATABASE'] || 'xsuportal', 12 | password: ENV['MYSQL_PASS'] || 'isucon', 13 | charset: 'utf8mb4', 14 | database_timezone: :utc, 15 | cast_booleans: true, 16 | symbolize_keys: true, 17 | reconnect: true, 18 | init_command: "SET time_zone='+00:00';", 19 | ) 20 | end 21 | 22 | team_id = nil 23 | status = 0 24 | hostname = 'xsu-001' 25 | option_parser = OptionParser.new do |opt| 26 | opt.banner = "Usage: #{__FILE__} -t team_id -h hostname -s status" 27 | opt.on('-t team_id') {|x| team_id = x } 28 | opt.on('-h hostname') {|x| hostname = x } 29 | opt.on('-s status') {|x| status = x.to_i } 30 | end 31 | option_parser.parse! 32 | 33 | unless team_id && status 34 | abort option_parser.banner 35 | end 36 | 37 | db.xquery( 38 | 'INSERT INTO `benchmark_jobs` (`team_id`, `status`, `target_hostname`, `updated_at`, `created_at`) VALUES (?, ?, ?, NOW(6), NOW(6))', 39 | team_id, 40 | status, 41 | hostname, 42 | ) 43 | job_id = db.query('SELECT LAST_INSERT_ID()').first.first[1] 44 | 45 | puts "Inserted job id=#{job_id}, hostname=#{hostname}, status=#{status}" 46 | -------------------------------------------------------------------------------- /webapp/tools/show_notifications: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'optparse' 3 | require 'json' 4 | require 'mysql2' 5 | require 'mysql2-cs-bind' 6 | 7 | $: << File.expand_path('../ruby/lib', __dir__) 8 | require 'xsuportal/resources/notification_pb' 9 | 10 | def db 11 | @db ||= Mysql2::Client.new( 12 | host: ENV['MYSQL_HOSTNAME'] || '127.0.0.1', 13 | port: ENV['MYSQL_PORT'] || '3306', 14 | username: ENV['MYSQL_USER'] || 'isucon', 15 | database: ENV['MYSQL_DATABASE'] || 'xsuportal', 16 | password: ENV['MYSQL_PASS'] || 'isucon', 17 | charset: 'utf8mb4', 18 | database_timezone: :utc, 19 | cast_booleans: true, 20 | symbolize_keys: true, 21 | reconnect: true, 22 | init_command: "SET time_zone='+00:00';", 23 | ) 24 | end 25 | 26 | contestant_id = nil 27 | option_parser = OptionParser.new do |opt| 28 | opt.banner = "Usage: #{__FILE__} -c contestant_id" 29 | opt.on('-c contestant_id') {|x| contestant_id = x } 30 | end 31 | option_parser.parse! 32 | 33 | abort option_parser.banner unless contestant_id 34 | 35 | notifications = db.xquery( 36 | 'SELECT * FROM `notifications` WHERE `contestant_id` = ? ORDER BY `id`', 37 | contestant_id, 38 | ) 39 | 40 | notifications = notifications.map do |notification| 41 | notification.merge(Xsuportal::Proto::Resources::Notification.decode(notification[:encoded_message].unpack1('m0')).to_h) 42 | end 43 | 44 | puts notifications.to_json 45 | --------------------------------------------------------------------------------