├── development ├── .gitignore ├── php │ ├── php-fpm.d │ │ └── zz-docker.conf │ └── nginx │ │ └── conf.d │ │ ├── isuride-php.conf │ │ └── nginx.conf ├── matching.js ├── dockerfiles │ ├── Dockerfile.node │ ├── Dockerfile.go │ ├── Dockerfile.python │ ├── Dockerfile.ruby │ ├── Dockerfile.rust │ └── Dockerfile.php ├── nginx │ └── conf.d │ │ └── nginx.conf ├── compose-local.yml ├── compose-ruby.yml ├── compose-go.yml ├── compose-rust.yml ├── compose-python.yml ├── compose-perl.yml └── compose-node.yml ├── docs ├── .gitignore ├── client-application-simulator-image.png ├── prh-rule.yaml ├── package.json └── .textlintrc.json ├── webapp ├── perl │ ├── .gitignore │ ├── app.psgi │ ├── cpanfile │ └── lib │ │ ├── Kossy │ │ └── Isuride │ │ │ ├── Models.pm │ │ │ ├── Time.pm │ │ │ └── Handler │ │ │ └── Internal.pm │ │ └── Mojo │ │ └── Isuride │ │ ├── Models.pm │ │ ├── Time.pm │ │ └── Handler │ │ └── Internal.pm ├── python │ ├── app │ │ ├── __init__.py │ │ ├── sql.py │ │ └── utils.py │ ├── .python-version │ ├── gunicorn.conf.py │ ├── .pre-commit-config.yaml │ ├── pyproject.toml │ └── noxfile.py ├── ruby │ ├── .dockerignore │ ├── .gitignore │ ├── Gemfile │ ├── config.ru │ ├── lib │ │ └── isuride │ │ │ ├── initialize_handler.rb │ │ │ └── internal_handler.rb │ └── Gemfile.lock ├── rust │ ├── .gitignore │ └── Cargo.toml ├── php │ ├── .openapi-generator │ │ └── VERSION │ ├── var │ │ └── cache │ │ │ └── .gitignore │ ├── resource │ │ └── .gitignore │ ├── phpstan.neon.dist │ ├── config.yaml │ ├── app │ │ ├── middleware.php │ │ └── config.php │ ├── .openapi-generator-ignore │ ├── src │ │ ├── PaymentGateway │ │ │ ├── GetPaymentsResponseOne.php │ │ │ └── PostPaymentRequest.php │ │ ├── function.php │ │ ├── Database │ │ │ └── Model │ │ │ │ ├── PaymentToken.php │ │ │ │ ├── RideStatus.php │ │ │ │ ├── Coupon.php │ │ │ │ ├── ChairLocation.php │ │ │ │ ├── Owner.php │ │ │ │ ├── Chair.php │ │ │ │ ├── User.php │ │ │ │ ├── RetrievedAt.php │ │ │ │ ├── RideRequest.php │ │ │ │ └── Ride.php │ │ ├── Result │ │ │ ├── Ride.php │ │ │ └── ChairStats.php │ │ ├── Response │ │ │ └── ErrorResponse.php │ │ └── Foundation │ │ │ ├── Handlers │ │ │ └── HttpErrorHandler.php │ │ │ └── ResponseEmitter │ │ │ └── ResponseEmitter.php │ ├── phpcs.xml │ ├── .gitignore │ └── composer.json ├── payment_mock │ ├── go.mod │ └── Dockerfile ├── nodejs │ ├── src │ │ ├── types │ │ │ ├── util.ts │ │ │ └── hono.ts │ │ └── utils │ │ │ ├── random.ts │ │ │ └── integer.ts │ ├── tsconfig.json │ ├── .gitignore │ ├── package.json │ └── biome.json ├── sql │ ├── 3-initial-data.sql.gz │ ├── 0-init.sql │ └── init.sh └── go │ ├── go.mod │ ├── go.sum │ └── .gitignore ├── .gitignore ├── frontend ├── .prettierignore ├── prettierrc.js ├── app │ ├── api │ │ ├── api-base-url.ts │ │ └── api-parameters.ts │ ├── globals.d.ts │ ├── utils │ │ ├── get-cookie-value.ts │ │ ├── get-initial-data.ts │ │ └── post-message.ts │ ├── routes │ │ ├── simulator │ │ │ └── route.tsx │ │ ├── client │ │ │ └── route.tsx │ │ └── client_.register-payment │ │ │ └── route.tsx │ ├── entry.client.tsx │ ├── components │ │ ├── primitives │ │ │ ├── list │ │ │ │ ├── list.tsx │ │ │ │ └── list-item.tsx │ │ │ ├── error-message │ │ │ │ └── error-message.tsx │ │ │ ├── frame │ │ │ │ ├── form-frame.tsx │ │ │ │ ├── config-frame.tsx │ │ │ │ └── frame.tsx │ │ │ ├── smartphone │ │ │ │ └── smartphone.tsx │ │ │ ├── header │ │ │ │ └── header.tsx │ │ │ ├── rating │ │ │ │ └── rating.tsx │ │ │ ├── form │ │ │ │ └── date-input.tsx │ │ │ ├── menu │ │ │ │ └── pulldown.tsx │ │ │ └── text │ │ │ │ └── text.tsx │ │ ├── modules │ │ │ ├── date-text │ │ │ │ └── date-text.tsx │ │ │ ├── price-text │ │ │ │ └── price-text.tsx │ │ │ ├── modal-header │ │ │ │ └── moda-header.tsx │ │ │ ├── simulator-parts │ │ │ │ └── simulator-chair-status-label.tsx │ │ │ ├── price │ │ │ │ └── price.tsx │ │ │ └── location-button │ │ │ │ └── location-button.tsx │ │ ├── icon │ │ │ ├── copy.tsx │ │ │ ├── pin.tsx │ │ │ ├── human.tsx │ │ │ ├── schedule.tsx │ │ │ ├── account-switch.tsx │ │ │ ├── history.tsx │ │ │ ├── mobile.tsx │ │ │ ├── rating-star.tsx │ │ │ ├── desktop.tsx │ │ │ ├── isuride.tsx │ │ │ ├── money.tsx │ │ │ └── account.tsx │ │ └── hooks │ │ │ └── use-on-click-outside.ts │ └── types.ts ├── public │ ├── favicon.ico │ ├── favicon-32x32.png │ ├── images │ │ └── top-bg.png │ ├── favicon-128x128.png │ └── apple-touch-icon-180x180.png ├── .gitignore ├── postcss.config.js ├── Makefile ├── tailwind.config.ts ├── api-url.mjs ├── tsconfig.json └── README.md ├── bench ├── bin │ └── .gitignore ├── benchmarker │ ├── scenario │ │ ├── data │ │ │ └── user │ │ │ │ ├── 01JDJ4XN10E2CRZ37RNZ5GAFW6 │ │ │ │ ├── rides.json │ │ │ │ ├── estimated_3_10.json │ │ │ │ └── estimated_-11_10.json │ │ │ │ └── 01JDK5EFNGT8ZHMTQXQ4BNH8NQ │ │ │ │ ├── estimated_-11_10.json │ │ │ │ └── estimated_3_10.json │ │ ├── postvalidation.go │ │ └── validation.go │ ├── webapp │ │ ├── api │ │ │ ├── ogen.yaml │ │ │ ├── generate.go │ │ │ ├── oas_interfaces_gen.go │ │ │ ├── oas_parameters_gen.go │ │ │ └── oas_cfg_gen.go │ │ ├── client_static.go │ │ └── client_initialize.go │ └── world │ │ ├── owner_test.go │ │ ├── errors_test.go │ │ ├── context.go │ │ ├── coordinate_test.go │ │ └── world_event.go ├── benchrun │ ├── generate.go │ ├── proto │ │ └── isuxportal │ │ │ ├── services │ │ │ ├── bench │ │ │ │ ├── cancellation.proto │ │ │ │ ├── reporting.proto │ │ │ │ └── receiving.proto │ │ │ ├── common │ │ │ │ ├── storage.proto │ │ │ │ └── me.proto │ │ │ ├── contestant │ │ │ │ ├── cloud_formation.proto │ │ │ │ ├── dashboard.proto │ │ │ │ ├── instances.proto │ │ │ │ ├── clarifications.proto │ │ │ │ ├── benchmark.proto │ │ │ │ └── notifications.proto │ │ │ ├── registration │ │ │ │ ├── activate_coupon.proto │ │ │ │ ├── env_check.proto │ │ │ │ ├── join.proto │ │ │ │ ├── create_team.proto │ │ │ │ └── session.proto │ │ │ ├── admin │ │ │ │ ├── cloud_formation.proto │ │ │ │ ├── env_checks.proto │ │ │ │ ├── contestant_instances.proto │ │ │ │ ├── dashboard.proto │ │ │ │ ├── leaderboard_dump.proto │ │ │ │ ├── teams.proto │ │ │ │ ├── clarifications.proto │ │ │ │ └── benchmark.proto │ │ │ └── audience │ │ │ │ ├── team_list.proto │ │ │ │ └── dashboard.proto │ │ │ ├── resources │ │ │ ├── survey_response.proto │ │ │ ├── staff.proto │ │ │ ├── coupon.proto │ │ │ ├── clarification.proto │ │ │ ├── contestant.proto │ │ │ ├── contestant_instance.proto │ │ │ ├── contest.proto │ │ │ ├── env_check.proto │ │ │ ├── team.proto │ │ │ ├── benchmark_result.proto │ │ │ ├── notification.proto │ │ │ ├── leaderboard.proto │ │ │ └── benchmark_job.proto │ │ │ ├── misc │ │ │ ├── bot │ │ │ │ └── support.proto │ │ │ ├── bypass_token.proto │ │ │ └── leaderboard_etag.proto │ │ │ └── error.proto │ ├── buf.yaml │ ├── buf.lock │ ├── buf.gen.yaml │ └── env.go ├── main.go ├── aqua.yaml ├── tools.go ├── payment │ ├── verifier.go │ ├── payment_test.go │ └── payment.go ├── cmd │ ├── version.go │ └── root.go ├── internal │ ├── concurrent │ │ ├── chan.go │ │ ├── simple_set.go │ │ ├── wait_group.go │ │ └── simple_slice.go │ └── random │ │ └── source.go ├── Dockerfile ├── Taskfile.yml ├── entrypoint.sh └── .gitignore ├── browsercheck ├── .gitignore ├── package.json ├── utils.js ├── pnpm-lock.yaml └── config.js ├── envcheck ├── .gitignore ├── run-isucon-env-checker.sh └── isucon-env-checker │ ├── Taskfile.yml │ ├── data.go │ ├── main.go │ └── go.mod ├── provisioning ├── ansible │ ├── roles │ │ ├── bench │ │ │ ├── files │ │ │ │ └── .gitignore │ │ │ └── tasks │ │ │ │ └── main.yaml │ │ ├── webapp │ │ │ ├── files │ │ │ │ ├── .gitignore │ │ │ │ ├── isuride-go.service │ │ │ │ ├── isuride-payment_mock.service │ │ │ │ ├── isuride-node.service │ │ │ │ ├── isuride-rust.service │ │ │ │ ├── isuride.php-fpm.conf │ │ │ │ ├── isuride-perl.service │ │ │ │ ├── isuride-python.service │ │ │ │ ├── isuride-php.service │ │ │ │ ├── isuride-ruby.service │ │ │ │ └── isuride-matcher.service │ │ │ └── tasks │ │ │ │ ├── matcher.yaml │ │ │ │ ├── node.yaml │ │ │ │ ├── ruby.yaml │ │ │ │ ├── rust.yaml │ │ │ │ ├── perl.yaml │ │ │ │ ├── go.yaml │ │ │ │ ├── python.yaml │ │ │ │ ├── payment_mock.yaml │ │ │ │ └── php.yaml │ │ ├── base │ │ │ ├── files │ │ │ │ └── pubkey.conf │ │ │ └── tasks │ │ │ │ └── main.yml │ │ ├── envcheck │ │ │ ├── files │ │ │ │ ├── .gitignore │ │ │ │ ├── warmup.sh │ │ │ │ ├── run-isucon-env-checker.sh │ │ │ │ └── envcheck.service │ │ │ └── tasks │ │ │ │ └── main.yml │ │ ├── xbuild │ │ │ ├── files │ │ │ │ ├── .x │ │ │ │ └── .local.env │ │ │ └── tasks │ │ │ │ └── main.yml │ │ ├── isucon-user │ │ │ ├── templates │ │ │ │ └── env.sh │ │ │ └── tasks │ │ │ │ └── main.yml │ │ ├── nginx │ │ │ └── files │ │ │ │ └── etc │ │ │ │ └── nginx │ │ │ │ ├── tls │ │ │ │ ├── dummy.key │ │ │ │ └── dummy.crt │ │ │ │ └── sites-available │ │ │ │ ├── isuride-php.conf │ │ │ │ └── isuride.conf │ │ ├── mysql │ │ │ └── tasks │ │ │ │ └── main.yml │ │ ├── apt │ │ │ └── tasks │ │ │ │ └── main.yml │ │ └── isuadmin-user │ │ │ └── tasks │ │ │ └── main.yml │ ├── inventory │ │ └── localhost │ ├── benchmark.yml │ ├── ansible.cfg │ ├── application.yml │ ├── sandbox.ini │ ├── application-base.yml │ ├── application-deploy.yml │ ├── make_latest_files.sh │ └── README.md ├── packer │ └── Makefile └── README.md ├── .editorconfig ├── .perltidyrc ├── .vscode └── settings.json ├── .github └── disabled-workflows │ └── frontend.yml ├── Taskfile.yml └── LICENSE /development/.gitignore: -------------------------------------------------------------------------------- 1 | mysql -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /webapp/perl/.gitignore: -------------------------------------------------------------------------------- 1 | /local 2 | -------------------------------------------------------------------------------- /webapp/python/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _tmp 2 | webapp/public 3 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | app/api/ 2 | -------------------------------------------------------------------------------- /webapp/ruby/.dockerignore: -------------------------------------------------------------------------------- 1 | /.bundle 2 | -------------------------------------------------------------------------------- /webapp/ruby/.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle 2 | -------------------------------------------------------------------------------- /webapp/rust/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /bench/bin/.gitignore: -------------------------------------------------------------------------------- 1 | bench_linux_amd64 2 | -------------------------------------------------------------------------------- /webapp/python/.python-version: -------------------------------------------------------------------------------- 1 | 3.13.1 2 | -------------------------------------------------------------------------------- /webapp/php/.openapi-generator/VERSION: -------------------------------------------------------------------------------- 1 | 7.9.0 2 | -------------------------------------------------------------------------------- /webapp/php/var/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /browsercheck/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | screeenshots 3 | -------------------------------------------------------------------------------- /webapp/php/resource/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | *.json 3 | -------------------------------------------------------------------------------- /envcheck/.gitignore: -------------------------------------------------------------------------------- 1 | /isucon-env-checker/isucon-env-checker 2 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/bench/files/.gitignore: -------------------------------------------------------------------------------- 1 | bench_* 2 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/files/.gitignore: -------------------------------------------------------------------------------- 1 | *.tar.gz 2 | -------------------------------------------------------------------------------- /webapp/payment_mock/go.mod: -------------------------------------------------------------------------------- 1 | module payment_mock 2 | 3 | go 1.23 4 | -------------------------------------------------------------------------------- /frontend/prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/app/api/api-base-url.ts: -------------------------------------------------------------------------------- 1 | export const apiBaseURL = __API_BASE_URL__; 2 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/base/files/pubkey.conf: -------------------------------------------------------------------------------- 1 | PubkeyAcceptedAlgorithms=+ssh-rsa 2 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/envcheck/files/.gitignore: -------------------------------------------------------------------------------- 1 | envcheck.tar.gz 2 | envcheck 3 | -------------------------------------------------------------------------------- /webapp/nodejs/src/types/util.ts: -------------------------------------------------------------------------------- 1 | export type CountResult = { "COUNT(*)": number }; 2 | -------------------------------------------------------------------------------- /webapp/php/phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 4 3 | paths: 4 | - src 5 | -------------------------------------------------------------------------------- /webapp/php/config.yaml: -------------------------------------------------------------------------------- 1 | invokerPackage: "IsuRide" 2 | srcBasePath: src 3 | library: psr-18 4 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/xbuild/files/.x: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | . /home/isucon/.local.env 3 | exec "$@" -------------------------------------------------------------------------------- /bench/benchmarker/scenario/data/user/01JDJ4XN10E2CRZ37RNZ5GAFW6/rides.json: -------------------------------------------------------------------------------- 1 | { 2 | "rides": [] 3 | } 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon14/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon14/HEAD/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/images/top-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon14/HEAD/frontend/public/images/top-bg.png -------------------------------------------------------------------------------- /webapp/sql/3-initial-data.sql.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon14/HEAD/webapp/sql/3-initial-data.sql.gz -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | /public/build 6 | .env 7 | 8 | *.login-cache.json 9 | -------------------------------------------------------------------------------- /frontend/public/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon14/HEAD/frontend/public/favicon-128x128.png -------------------------------------------------------------------------------- /bench/benchrun/generate.go: -------------------------------------------------------------------------------- 1 | package benchrun 2 | 3 | //go:generate go run github.com/bufbuild/buf/cmd/buf@v1.45.0 generate 4 | -------------------------------------------------------------------------------- /bench/benchmarker/webapp/api/ogen.yaml: -------------------------------------------------------------------------------- 1 | generator: 2 | features: 3 | disable_all: true 4 | enable: 5 | - paths/client -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /bench/benchmarker/scenario/data/user/01JDJ4XN10E2CRZ37RNZ5GAFW6/estimated_3_10.json: -------------------------------------------------------------------------------- 1 | { 2 | "fare": 500, 3 | "discount": 1300 4 | } 5 | -------------------------------------------------------------------------------- /bench/benchmarker/scenario/data/user/01JDK5EFNGT8ZHMTQXQ4BNH8NQ/estimated_-11_10.json: -------------------------------------------------------------------------------- 1 | { 2 | "fare": 2600, 3 | "discount": 0 4 | } 5 | -------------------------------------------------------------------------------- /bench/benchmarker/scenario/data/user/01JDK5EFNGT8ZHMTQXQ4BNH8NQ/estimated_3_10.json: -------------------------------------------------------------------------------- 1 | { 2 | "fare": 1800, 3 | "discount": 0 4 | } 5 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/bench/cancellation.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.bench; 3 | 4 | -------------------------------------------------------------------------------- /docs/client-application-simulator-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon14/HEAD/docs/client-application-simulator-image.png -------------------------------------------------------------------------------- /bench/benchmarker/scenario/data/user/01JDJ4XN10E2CRZ37RNZ5GAFW6/estimated_-11_10.json: -------------------------------------------------------------------------------- 1 | { 2 | "fare": 1100, 3 | "discount": 1500 4 | } 5 | -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isucon/isucon14/HEAD/frontend/public/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /webapp/php/app/middleware.php: -------------------------------------------------------------------------------- 1 | /dev/null 4 | find /home/isucon/webapp/sql -type f | xargs cat > /dev/null -------------------------------------------------------------------------------- /webapp/nodejs/src/utils/random.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes } from "node:crypto"; 2 | 3 | export const secureRandomStr = (length: number): string => 4 | randomBytes(length).toString("hex"); 5 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/staff.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | message Staff { 6 | int64 id = 1; 7 | string github_login = 2; 8 | } 9 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/bench/tasks/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Send benchmarker binary 3 | become: true 4 | copy: 5 | src: bench_linux_amd64 6 | dest: /home/isucon/ 7 | mode: 0755 8 | -------------------------------------------------------------------------------- /webapp/python/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.8.1 4 | hooks: 5 | - id: ruff 6 | args: [ --fix ] 7 | - id: ruff-format 8 | -------------------------------------------------------------------------------- /frontend/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"], 5 | plugins: [], 6 | } satisfies Config; 7 | -------------------------------------------------------------------------------- /bench/payment/verifier.go: -------------------------------------------------------------------------------- 1 | package payment 2 | 3 | //go:generate go run go.uber.org/mock/mockgen -typed -source=$GOFILE -package=$GOPACKAGE -destination=./mock_$GOFILE 4 | 5 | type Verifier interface { 6 | Verify(p *Payment) Status 7 | } 8 | -------------------------------------------------------------------------------- /provisioning/ansible/application-base.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # ランタイムのインストールまでを行う 3 | # packerのビルドをわける場合につかう 4 | - hosts: application 5 | roles: 6 | - base 7 | - apt 8 | - isucon-user 9 | - xbuild 10 | - xbuildwebapp 11 | -------------------------------------------------------------------------------- /webapp/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'bigdecimal' 6 | gem 'mysql2' 7 | gem 'mysql2-cs-bind' 8 | gem 'puma' 9 | gem 'sinatra' 10 | gem 'sinatra-contrib' 11 | gem 'ulid' 12 | -------------------------------------------------------------------------------- /envcheck/run-isucon-env-checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | retried=0 5 | while [[ $retried -le 15 ]]; do 6 | sleep $(( RANDOM % 15 )) 7 | /usr/local/bin/isucon-env-checker boot && exit 0 8 | retried=$(( retried + 1 )) 9 | done 10 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/isucon-user/templates/env.sh: -------------------------------------------------------------------------------- 1 | ISUCON_DB_HOST="127.0.0.1" 2 | ISUCON_DB_PORT="3306" 3 | ISUCON_DB_USER="isucon" 4 | ISUCON_DB_PASSWORD="isucon" 5 | ISUCON_DB_NAME="isuride" 6 | 7 | # マッチング間隔(秒) 8 | ISUCON_MATCHING_INTERVAL=0.5 9 | -------------------------------------------------------------------------------- /bench/benchrun/buf.yaml: -------------------------------------------------------------------------------- 1 | # For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml 2 | version: v2 3 | modules: 4 | - name: isucon/isucon-14/isuxportal 5 | path: proto 6 | 7 | deps: 8 | - buf.build/bufbuild/protovalidate -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/coupon.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | message Coupon { 6 | int64 id = 1; 7 | int64 team_id = 2; 8 | repeated string code = 3; 9 | bool activate = 4; 10 | } 11 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/common/storage.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.common; 3 | 4 | message GetAvatarUrlRequest { 5 | } 6 | 7 | message GetAvatarUrlResponse { 8 | string upload_presigned = 1; 9 | string url = 2; 10 | } -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/contestant/cloud_formation.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.contestant; 3 | 4 | 5 | message GetCloudFormationQuery {} 6 | 7 | message GetCloudFormationResponse { 8 | string template = 1; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/app/utils/get-cookie-value.ts: -------------------------------------------------------------------------------- 1 | export const getCookieValue = (cookieString: string, cookieName: string) => { 2 | const regex = new RegExp(`(?:^|; )${cookieName}=([^;]*)`); 3 | const match = cookieString.match(regex); 4 | return match ? match[1] : undefined; 5 | }; 6 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/envcheck/files/run-isucon-env-checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | retried=0 5 | while [[ $retried -le 15 ]]; do 6 | sleep $(( RANDOM % 15 )) 7 | /opt/isucon-env-checker/envcheck boot && exit 0 8 | retried=$(( retried + 1 )) 9 | done 10 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/registration/activate_coupon.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.registration; 3 | 4 | 5 | message ActivateCouponRequest { 6 | int64 team_id = 1; 7 | } 8 | 9 | message ActivateCouponResponse {} 10 | 11 | 12 | -------------------------------------------------------------------------------- /browsercheck/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browsercheck", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "start": "node run.js" 7 | }, 8 | "devDependencies": { 9 | "playwright-chromium": "^1.49.0" 10 | }, 11 | "packageManager": "pnpm@9.14.4" 12 | } 13 | -------------------------------------------------------------------------------- /development/matching.js: -------------------------------------------------------------------------------- 1 | // while true; do curl -s http://nginx/api/internal/matching; sleep 0.5; done と同じことをnodejs で実装 2 | 3 | const f = () => { 4 | try { 5 | fetch("http://localhost:8080/api/internal/matching"); 6 | } catch (e) {} 7 | }; 8 | 9 | setInterval(f, 500); 10 | -------------------------------------------------------------------------------- /provisioning/ansible/application-deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # webappのデプロイ 3 | # 古いwebappディレクトリを消して作り直す 4 | - hosts: application 5 | pre_tasks: 6 | - name: remove webapp 7 | become: true 8 | shell: | 9 | rm -rf /home/isucon/webapp/ 10 | roles: 11 | - webapp 12 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/nginx/files/etc/nginx/tls/dummy.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg1kl5A2NG4Vtz8VWt 3 | c291ZIxeu0RxvakxHf9Gv+P+NrGhRANCAAQEiaEkT85s5L7NH8/JDgH2MeRf/xI2 4 | DBGvRFqKSBM3Kqw4T/b9licanhyFdSHPzMKAvbHNZh9k8yjtzrXJoMdY 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/misc/bot/support.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.misc.bot; 3 | 4 | 5 | message SupportCommInfo { 6 | message Usage { 7 | uint64 message_id_since = 1; 8 | int64 team_id = 2; 9 | int64 clarification_id = 3; 10 | } 11 | Usage usage = 2; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/app/routes/simulator/route.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "@remix-run/react"; 2 | 3 | import { SimulatorProvider } from "~/contexts/simulator-context"; 4 | 5 | export default function Layout() { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /webapp/nodejs/src/types/hono.ts: -------------------------------------------------------------------------------- 1 | import type { PoolConnection } from "mysql2/promise"; 2 | import type { Chair, Owner, User } from "./models.js"; 3 | 4 | export type Environment = { 5 | Variables: { 6 | dbConn: PoolConnection; 7 | user: User; 8 | owner: Owner; 9 | chair: Chair; 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /bench/benchrun/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v2 3 | deps: 4 | - name: buf.build/bufbuild/protovalidate 5 | commit: 5a7b106cbb87462d9a8c9ffecdbd2e38 6 | digest: b5:0f2dc6c9453e9cc9e9f36807aaa5f94022e837d91fef4dcaeed79a35c0843cc64eba28ff077aab24da3b2cb12639ad256246f9f9a36c033b99d5754b19996b7e 7 | -------------------------------------------------------------------------------- /webapp/go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/isucon/isucon14/webapp/go 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/go-chi/chi/v5 v5.1.0 7 | github.com/go-sql-driver/mysql v1.8.1 8 | github.com/jmoiron/sqlx v1.4.0 9 | github.com/oklog/ulid/v2 v2.1.0 10 | ) 11 | 12 | require filippo.io/edwards25519 v1.1.0 // indirect 13 | -------------------------------------------------------------------------------- /webapp/nodejs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2024", 4 | "module": "nodenext", 5 | "outDir": "dist", 6 | "strict": true, 7 | "verbatimModuleSyntax": true, 8 | "skipLibCheck": true, 9 | "moduleResolution": "nodenext", 10 | "types": ["node"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /envcheck/isucon-env-checker/Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | tasks: 4 | build: 5 | cmds: 6 | - go build -ldflags "-s -w" 7 | build-linux-amd64: 8 | cmds: 9 | - GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o ./envcheck 10 | - mv envcheck ../../provisioning/ansible/roles/envcheck/files/ 11 | -------------------------------------------------------------------------------- /webapp/php/.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | composer.json 2 | public/* 3 | config/** 4 | logs/* 5 | *.xml.dist 6 | test/** 7 | docs/** 8 | README.md 9 | .github/** 10 | *.yml 11 | .php-cs-fixer.dist.php 12 | *.sh 13 | src/Api/** 14 | src/ApiException.php 15 | src/Configuration.php 16 | src/HeaderSelector.php 17 | src/DebugPlugin.php 18 | -------------------------------------------------------------------------------- /webapp/php/src/PaymentGateway/GetPaymentsResponseOne.php: -------------------------------------------------------------------------------- 1 | n { 10 | n = num 11 | t.Logf("num: %d (original: %d), sales: %d", num, i*100/15000, i*100) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/admin/cloud_formation.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.admin; 3 | 4 | 5 | message GetCloudFormationQuery { 6 | int64 id = 1; 7 | string type = 2; // query parameter. "test" | "contest" 8 | } 9 | 10 | message GetCloudFormationResponse { 11 | string template = 1; 12 | } 13 | -------------------------------------------------------------------------------- /development/dockerfiles/Dockerfile.node: -------------------------------------------------------------------------------- 1 | FROM node:22.11 2 | 3 | WORKDIR /home/isucon/webapp/nodejs 4 | 5 | RUN apt-get update && apt-get install --no-install-recommends -y \ 6 | default-mysql-client-core=1.1.0 \ 7 | && apt-get clean \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | COPY . . 11 | RUN npm ci 12 | 13 | CMD ["npm", "run", "start"] 14 | -------------------------------------------------------------------------------- /frontend/app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import { RemixBrowser } from "@remix-run/react"; 2 | import { startTransition, StrictMode } from "react"; 3 | import { hydrateRoot } from "react-dom/client"; 4 | 5 | startTransition(() => { 6 | hydrateRoot( 7 | document, 8 | 9 | 10 | , 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /webapp/nodejs/src/utils/integer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Goのstrconv.Atoiを模した関数 3 | * Integerを変換した値を返す 4 | * Integerに変換できない値が来たときはfalseを返す 5 | */ 6 | export const atoi = (string_: string): number | false => { 7 | const number_ = Number(string_); 8 | const isInt = Number.isInteger(number_); 9 | if (isInt) return number_; 10 | return false; 11 | }; 12 | -------------------------------------------------------------------------------- /webapp/php/src/Database/Model/PaymentToken.php: -------------------------------------------------------------------------------- 1 | >> = ({ 4 | children, 5 | className, 6 | ...props 7 | }) => { 8 | return ( 9 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.go] 12 | indent_style = tab 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | 17 | [*.php] 18 | indent_size = 4 19 | 20 | [*.py] 21 | indent_size = 4 22 | -------------------------------------------------------------------------------- /development/dockerfiles/Dockerfile.go: -------------------------------------------------------------------------------- 1 | FROM golang:1.23 2 | 3 | WORKDIR /home/isucon/webapp/go 4 | 5 | RUN apt-get update && apt-get install --no-install-recommends -y \ 6 | default-mysql-client-core=1.1.0 \ 7 | && apt-get clean \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | COPY . . 11 | RUN go build -o webapp . 12 | 13 | CMD ["/home/isucon/webapp/go/webapp"] 14 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/misc/bypass_token.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.misc; 3 | 4 | 5 | message BypassTokenPayload { 6 | string filler = 1; 7 | int64 expiry = 2; 8 | enum Usage { 9 | CREATE_TEAM = 0; 10 | JOIN_TEAM = 1; 11 | HIDDEN_TEAM = 2; 12 | LEAVE_TEAM = 3; 13 | } 14 | repeated Usage usages = 3; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/error-message/error-message.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren, FC } from "react"; 2 | 3 | export const ErrorMessage: FC = ({ children }) => { 4 | return ( 5 |
6 |

{children}

7 |
8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /bench/benchmarker/world/errors_test.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCodeError_Is(t *testing.T) { 12 | assert.True(t, errors.Is(WrapCodeError(ErrorCodeFailedToSendChairCoordinate, io.ErrUnexpectedEOF), CodeError(ErrorCodeFailedToSendChairCoordinate))) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/admin/env_checks.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.admin; 3 | 4 | 5 | import "isuxportal/resources/env_check.proto"; 6 | 7 | message ListEnvChecksQuery { 8 | int64 team_id = 1; 9 | } 10 | 11 | message ListEnvChecksResponse { 12 | repeated isuxportal.proto.resources.EnvCheck env_checks = 1; 13 | } 14 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/registration/env_check.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.registration; 3 | 4 | 5 | import "isuxportal/resources/env_check.proto"; 6 | 7 | message GetEnvCheckInformationResponse { 8 | string template = 1; 9 | isuxportal.proto.resources.EnvCheckStatus status = 2; 10 | string instance_ip = 3; 11 | } 12 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/envcheck/files/envcheck.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Check Environments for ISUCON14 Contestants 3 | After=network.target 4 | 5 | [Service] 6 | Type=oneshot 7 | RemainAfterExit=yes 8 | ExecStartPre=/opt/isucon-env-checker/warmup.sh 9 | ExecStart=/opt/isucon-env-checker/run-isucon-env-checker.sh 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /webapp/php/src/Database/Model/RideStatus.php: -------------------------------------------------------------------------------- 1 | >> = ({ 5 | children, 6 | className, 7 | ...props 8 | }) => { 9 | return ( 10 |
  • 11 | {children} 12 |
  • 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /webapp/python/app/sql.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import sqlalchemy 4 | 5 | host = os.getenv("ISUCON_DB_HOST", "127.0.0.1") 6 | port = int(os.getenv("ISUCON_DB_PORT", "3306")) 7 | user = os.getenv("ISUCON_DB_USER", "isucon") 8 | password = os.getenv("ISUCON_DB_PASSWORD", "isucon") 9 | dbname = os.getenv("ISUCON_DB_NAME", "isuride") 10 | 11 | engine = sqlalchemy.create_engine( 12 | f"mysql+pymysql://{user}:{password}@{host}:{port}/{dbname}" 13 | ) 14 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/frame/form-frame.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, PropsWithChildren } from "react"; 2 | 3 | export const FormFrame: FC = ({ children }) => { 4 | return ( 5 |
    6 |
    7 | {children} 8 |
    9 |
    10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /webapp/php/src/Database/Model/Owner.php: -------------------------------------------------------------------------------- 1 | $this->amount]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/admin/contestant_instances.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.admin; 3 | 4 | 5 | import "isuxportal/resources/contestant_instance.proto"; 6 | 7 | message ListContestantInstancesQuery { 8 | // optional filter by team_id 9 | int64 team_id = 1; 10 | } 11 | 12 | message ListContestantInstancesResponse { 13 | repeated isuxportal.proto.resources.ContestantInstance contestant_instances = 14 | 1; 15 | } 16 | -------------------------------------------------------------------------------- /webapp/nodejs/.gitignore: -------------------------------------------------------------------------------- 1 | # dev 2 | .yarn/ 3 | !.yarn/releases 4 | .vscode/* 5 | !.vscode/launch.json 6 | !.vscode/*.code-snippets 7 | .idea/workspace.xml 8 | .idea/usage.statistics.xml 9 | .idea/shelf 10 | 11 | # deps 12 | node_modules/ 13 | 14 | # env 15 | .env 16 | .env.production 17 | 18 | # logs 19 | logs/ 20 | *.log 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | pnpm-debug.log* 25 | lerna-debug.log* 26 | 27 | # misc 28 | .DS_Store 29 | 30 | dist/ 31 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/error.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto; 3 | 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 | -------------------------------------------------------------------------------- /webapp/perl/app.psgi: -------------------------------------------------------------------------------- 1 | use v5.40; 2 | use FindBin; 3 | use lib "$FindBin::Bin/lib"; 4 | 5 | # IsurideのKossyによる実装 6 | # Kossy::Isuride名前空間のpackageを利用する 7 | # 8 | # Mojolicious::Liteによる実装はapp.plを起動すること 9 | # 10 | 11 | use Plack::Builder; 12 | use Kossy::Isuride::Web; 13 | use File::Basename; 14 | 15 | my $root_dir = File::Basename::dirname(__FILE__); 16 | 17 | my $app = Kossy::Isuride::Web->psgi($root_dir); 18 | 19 | builder { 20 | enable 'ReverseProxy'; 21 | $app; 22 | }; 23 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/registration/create_team.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.registration; 3 | 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 | bool is_in_person = 5; 11 | string avatar_url = 6; 12 | bool hidden = 16; // requires bypass token 13 | } 14 | 15 | message CreateTeamResponse { int64 team_id = 1; } 16 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/nginx/files/etc/nginx/sites-available/isuride-php.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | 4 | client_max_body_size 10m; 5 | root /home/isucon/webapp/php/public/; 6 | 7 | location / { 8 | try_files $uri /index.php$is_args$args; 9 | } 10 | 11 | location = /index.php { 12 | include fastcgi_params; 13 | fastcgi_index index.php; 14 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 15 | fastcgi_pass 127.0.0.1:9000; 16 | } 17 | } -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/files/isuride-go.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isuride-go 3 | After=syslog.target 4 | After=mysql.service 5 | Requires=mysql.service 6 | 7 | [Service] 8 | WorkingDirectory=/home/isucon/webapp/go 9 | EnvironmentFile=/home/isucon/env.sh 10 | 11 | User=isucon 12 | Group=isucon 13 | ExecStart=/home/isucon/webapp/go/isuride 14 | ExecStop=/bin/kill -s QUIT $MAINPID 15 | 16 | Restart=on-failure 17 | RestartSec=5 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/audience/dashboard.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.audience; 3 | 4 | 5 | import "isuxportal/resources/leaderboard.proto"; 6 | 7 | message DashboardQuery {} 8 | 9 | message DashboardResponse { 10 | isuxportal.proto.resources.Leaderboard leaderboard = 1; 11 | } 12 | 13 | message SoloDashboardQuery {} 14 | 15 | message SoloDashboardResponse { 16 | isuxportal.proto.resources.LeaderboardItem leaderboard_item = 1; 17 | } 18 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/files/isuride-payment_mock.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isuride-payment_mock 3 | After=syslog.target 4 | After=mysql.service 5 | Requires=mysql.service 6 | 7 | [Service] 8 | WorkingDirectory=/home/isucon/webapp/payment_mock 9 | 10 | User=isucon 11 | Group=isucon 12 | ExecStart=/home/isucon/webapp/payment_mock/payment_mock 13 | ExecStop=/bin/kill -s QUIT $MAINPID 14 | 15 | Restart=on-failure 16 | RestartSec=5 17 | 18 | [Install] 19 | WantedBy=multi-user.target 20 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/files/isuride-node.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isuride-node 3 | After=syslog.target 4 | After=mysql.service 5 | Requires=mysql.service 6 | 7 | [Service] 8 | WorkingDirectory=/home/isucon/webapp/nodejs 9 | EnvironmentFile=/home/isucon/env.sh 10 | 11 | User=isucon 12 | Group=isucon 13 | ExecStart=/home/isucon/.x npm run start 14 | ExecStop=/bin/kill -s QUIT $MAINPID 15 | 16 | Restart=on-failure 17 | RestartSec=5 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/xbuild/files/.local.env: -------------------------------------------------------------------------------- 1 | export PATH=/home/isucon/local/golang/bin:$PATH 2 | export PATH=/home/isucon/local/node/bin:$PATH 3 | export PATH=/home/isucon/.cargo/bin:$PATH 4 | export PATH=/home/isucon/local/php/bin:/home/isucon/local/php/sbin:$PATH 5 | export PATH=/home/isucon/local/ruby/bin:$PATH 6 | export PATH=/home/isucon/local/perl/bin:/home/isucon/webapp/perl/local/bin:$PATH 7 | export PATH=/home/isucon/local/python/bin:$PATH 8 | export PERL5LIB=/home/isucon/webapp/perl/local/lib/perl5 9 | -------------------------------------------------------------------------------- /webapp/php/phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Slim coding standard 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | src 16 | tests 17 | -------------------------------------------------------------------------------- /envcheck/isucon-env-checker/data.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go-v2/service/ec2" 5 | ) 6 | 7 | type Data struct { 8 | ExpectedAMIID string 9 | ExpectedAZID string 10 | 11 | InstanceVPCID string 12 | 13 | DescribeInstances []*ec2.DescribeInstancesOutput 14 | DescribeVolumes []*ec2.DescribeVolumesOutput 15 | DescribeNetworkInterfaces []*ec2.DescribeNetworkInterfacesOutput 16 | 17 | DescribeAvailabilityZones *ec2.DescribeAvailabilityZonesOutput 18 | } 19 | -------------------------------------------------------------------------------- /webapp/php/src/Database/Model/Chair.php: -------------------------------------------------------------------------------- 1 | =3.13" 5 | dependencies = [ 6 | "fastapi>=0.115.5", 7 | "gunicorn>=23.0.0", 8 | "pymysql[rsa]>=1.1.1", 9 | "python-ulid>=3.0.0", 10 | "sqlalchemy>=2.0.36", 11 | "urllib3>=2.2.3", 12 | "uvicorn-worker>=0.2.0", 13 | ] 14 | 15 | [tool.ruff.lint] 16 | extend-select = [ 17 | "UP", # pyupgrade 18 | "I", # isort 19 | "FAST", # FastAPI 20 | ] 21 | 22 | [tool.mypy] 23 | strict = true 24 | -------------------------------------------------------------------------------- /webapp/php/src/Database/Model/User.php: -------------------------------------------------------------------------------- 1 | { 7 | const pathname = new URL(page.url()).pathname; 8 | const filenameBase = pathname.slice(1).replaceAll("/", "_"); 9 | 10 | await page.locator(selector).waitFor({ state: "visible" }); 11 | await page.screenshot({ 12 | path: `screeenshots/${teamId}/${filenameBase}.png`, 13 | fullPage: true, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /development/php/nginx/conf.d/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | 4 | client_max_body_size 10m; 5 | root /home/isucon/webapp/public/; 6 | location / { 7 | try_files $uri /index.html; 8 | } 9 | location /api/ { 10 | proxy_set_header Host $host; 11 | proxy_pass http://localhost:8080; 12 | } 13 | 14 | location /api/internal/ { 15 | # dockerからのみアクセスを許可 16 | allow 172.16.0.0/12; 17 | deny all; 18 | proxy_set_header Host $host; 19 | proxy_pass http://localhost:8080; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bench/benchmarker/world/coordinate_test.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import ( 4 | "math/rand/v2" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRandomCoordinateAwayFromHereWithRand(t *testing.T) { 11 | r := rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64())) 12 | 13 | c := C(0, 0) 14 | for range 1000 { 15 | prev := c 16 | distance := r.IntN(100) 17 | c = RandomCoordinateAwayFromHereWithRand(c, distance, r) 18 | assert.Equal(t, distance, prev.DistanceTo(c), "離れる量は常にdistanceと一致しなければならない") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/misc/leaderboard_etag.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.misc; 3 | 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | message LeaderboardEtag { 8 | bool admin = 1; 9 | int64 team_id = 2; 10 | int64 team_count = 3; 11 | google.protobuf.Timestamp team_last_updated = 4; 12 | int64 latest_result_id = 5; 13 | int64 latest_progress_id = 6; 14 | bool has_progress = 7; 15 | google.protobuf.Timestamp latest_result_at = 8; 16 | google.protobuf.Timestamp latest_progress_at = 9; 17 | } 18 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/files/isuride-php.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isuride-php 3 | After=syslog.target 4 | After=mysql.service 5 | Requires=mysql.service 6 | 7 | [Service] 8 | WorkingDirectory=/home/isucon/webapp/php 9 | EnvironmentFile=/home/isucon/env.sh 10 | 11 | User=isucon 12 | Group=isucon 13 | ExecStart=/home/isucon/.x php-fpm --fpm-config /home/isucon/local/php/etc/isuride.php-fpm.conf 14 | ExecStop=/bin/kill -s QUIT $MAINPID 15 | 16 | Restart=on-failure 17 | RestartSec=5 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/frame/config-frame.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC, PropsWithChildren } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export const ConfigFrame: FC>> = ({ 5 | children, 6 | className, 7 | ...props 8 | }) => { 9 | return ( 10 |
    17 | {children} 18 |
    19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/mysql/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install MySQL 2 | become: true 3 | apt: 4 | name: mysql-server 5 | state: present 6 | 7 | - name: Start mysql service 8 | service: 9 | name: mysql 10 | enabled: true 11 | state: started 12 | 13 | - name: Create isucon user on MySQL 14 | become: true 15 | shell: > 16 | mysql -uroot -e " 17 | CREATE USER IF NOT EXISTS 'isucon'@'%' IDENTIFIED BY 'isucon'; 18 | GRANT ALL PRIVILEGES ON *.* TO 'isucon'@'%' WITH GRANT OPTION; 19 | FLUSH PRIVILEGES; 20 | " 21 | -------------------------------------------------------------------------------- /webapp/python/noxfile.py: -------------------------------------------------------------------------------- 1 | import nox 2 | 3 | 4 | @nox.session(python="3.13") 5 | def lint(session: nox.Session) -> None: 6 | session.install("pre-commit") 7 | session.run("pre-commit", "run", "--all-files") 8 | 9 | 10 | @nox.session(python="3.13") 11 | def mypy(session: nox.Session) -> None: 12 | session.install( 13 | "mypy", 14 | "cryptography", 15 | "fastapi", 16 | "python-ulid", 17 | "sqlalchemy", 18 | "urllib3", 19 | ) 20 | session.run( 21 | "mypy", 22 | "app", 23 | ) 24 | -------------------------------------------------------------------------------- /frontend/app/components/modules/date-text/date-text.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentPropsWithoutRef, FC } from "react"; 2 | import { Text } from "~/components/primitives/text/text"; 3 | 4 | type DateTextProps = Omit, "children"> & { 5 | value: number; 6 | }; 7 | 8 | const formatter = new Intl.DateTimeFormat("ja-JP", { 9 | dateStyle: "medium", 10 | timeZone: "Asia/Tokyo", 11 | }); 12 | 13 | export const DateText: FC = ({ value, ...rest }) => { 14 | return {formatter.format(value)}; 15 | }; 16 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/contestant/clarifications.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.contestant; 3 | 4 | 5 | import "isuxportal/resources/clarification.proto"; 6 | 7 | message ListClarificationsQuery {} 8 | 9 | message ListClarificationsResponse { 10 | repeated isuxportal.proto.resources.Clarification clarifications = 1; 11 | } 12 | 13 | message RequestClarificationRequest { string question = 1; } 14 | 15 | message RequestClarificationResponse { 16 | isuxportal.proto.resources.Clarification clarification = 1; 17 | } 18 | -------------------------------------------------------------------------------- /bench/internal/concurrent/chan.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import "iter" 4 | 5 | // TryIter ブロッキング無しでchから値が取り出せるだけ取り出すイテレーターを返します 6 | func TryIter[T any](ch <-chan T) iter.Seq[T] { 7 | return func(yield func(T) bool) { 8 | for { 9 | select { 10 | case v := <-ch: 11 | if !yield(v) { 12 | return 13 | } 14 | default: 15 | return 16 | } 17 | } 18 | } 19 | } 20 | 21 | // TrySend ブロッキング無しのchへの送信を試みます 22 | func TrySend[T any](ch chan<- T, v T) bool { 23 | select { 24 | case ch <- v: 25 | return true 26 | default: 27 | return false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/app/components/icon/copy.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | 3 | export const CopyIcon: FC> = (props) => { 4 | return ( 5 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /bench/benchmarker/world/world_event.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | type Event interface { 4 | isWorldEvent() 5 | } 6 | 7 | type unimplementedEvent struct{} 8 | 9 | func (*unimplementedEvent) isWorldEvent() {} 10 | 11 | type EventRequestCompleted struct { 12 | Request *Request 13 | 14 | unimplementedEvent 15 | } 16 | 17 | type EventUserActivated struct { 18 | User *User 19 | 20 | unimplementedEvent 21 | } 22 | 23 | type EventUserLeave struct { 24 | User *User 25 | 26 | unimplementedEvent 27 | } 28 | 29 | type EventSoftError struct { 30 | Error error 31 | 32 | unimplementedEvent 33 | } 34 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/clarification.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | import "isuxportal/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 | string original_question = 9; 18 | bool admin = 10; 19 | 20 | Team team = 16; 21 | } 22 | -------------------------------------------------------------------------------- /webapp/ruby/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift(File.join('lib', __dir__)) 4 | 5 | require 'isuride/app_handler' 6 | require 'isuride/chair_handler' 7 | require 'isuride/initialize_handler' 8 | require 'isuride/internal_handler' 9 | require 'isuride/owner_handler' 10 | 11 | map '/api/app/' do 12 | run Isuride::AppHandler 13 | end 14 | map '/api/chair/' do 15 | use Isuride::ChairHandler 16 | end 17 | map '/api/owner/' do 18 | use Isuride::OwnerHandler 19 | end 20 | map '/api/internal/' do 21 | use Isuride::InternalHandler 22 | end 23 | run Isuride::InitializeHandler 24 | -------------------------------------------------------------------------------- /development/dockerfiles/Dockerfile.ruby: -------------------------------------------------------------------------------- 1 | FROM ruby:3.3.6-bookworm 2 | 3 | WORKDIR /home/isucon/webapp/ruby 4 | 5 | RUN apt-get update && apt-get install --no-install-recommends -y \ 6 | default-mysql-client-core=1.1.0 \ 7 | && apt-get clean \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | COPY Gemfile Gemfile.lock ./ 11 | ENV BUNDLE_DEPLOYMENT=1 BUNDLE_PATH=/gems BUNDLE_JOBS=8 12 | RUN bundle install 13 | 14 | COPY . . 15 | 16 | ENV RUBY_YJIT_ENABLE=1 17 | 18 | EXPOSE 8080 19 | CMD ["bundle", "exec", "puma", "--bind", "tcp://0.0.0.0:8080", "--workers", "8", "--threads", "0:8", "--environment", "production"] 20 | -------------------------------------------------------------------------------- /development/dockerfiles/Dockerfile.rust: -------------------------------------------------------------------------------- 1 | FROM rust:1.83-bookworm 2 | 3 | WORKDIR /home/isucon/webapp/rust 4 | 5 | RUN apt-get update && apt-get install --no-install-recommends -y \ 6 | default-mysql-client-core=1.1.0 \ 7 | && apt-get clean \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | COPY ./Cargo.toml ./Cargo.lock ./ 11 | RUN mkdir src && echo 'fn main() {}' > ./src/main.rs && cargo build --release --locked && rm src/main.rs target/release/deps/isuride-* 12 | COPY ./src/ ./src/ 13 | RUN cargo build --release --locked --frozen 14 | 15 | EXPOSE 8080 16 | CMD ["/home/isucon/webapp/rust/target/release/isuride"] 17 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/files/isuride-ruby.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isuride-ruby 3 | After=syslog.target 4 | After=mysql.service 5 | Requires=mysql.service 6 | 7 | [Service] 8 | WorkingDirectory=/home/isucon/webapp/ruby 9 | Environment=RUBY_YJIT_ENABLE=1 10 | EnvironmentFile=/home/isucon/env.sh 11 | 12 | User=isucon 13 | Group=isucon 14 | ExecStart=/home/isucon/.x bundle exec puma --bind tcp://0.0.0.0:8080 --workers 8 --threads 0:8 --environment production 15 | ExecStop=/bin/kill -s QUIT $MAINPID 16 | 17 | Restart=on-failure 18 | RestartSec=5 19 | 20 | [Install] 21 | WantedBy=multi-user.target 22 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/contestant.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | message Contestant { 6 | int64 id = 1; 7 | int64 team_id = 2; 8 | string name = 3; 9 | message ContestantDetail { 10 | string github_login = 1; 11 | string discord_tag = 2; 12 | bool is_student = 3; 13 | string avatar_url = 4; 14 | bool is_in_person = 5; 15 | 16 | string github_id = 16; 17 | string discord_id = 17; 18 | 19 | bool is_ssh_key_registered = 21; 20 | bool is_discord_guild_member = 22; 21 | } 22 | ContestantDetail detail = 7; 23 | } 24 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/contestant_instance.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | import "isuxportal/resources/team.proto"; 6 | 7 | message ContestantInstance { 8 | int64 id = 7; 9 | string cloud_id = 1; 10 | int64 team_id = 2; 11 | int64 number = 3; 12 | string public_ipv4_address = 4; 13 | string private_ipv4_address = 5; 14 | 15 | Status status = 6; 16 | enum Status { 17 | UNKNOWN = 0; 18 | PENDING = 1; 19 | MODIFYING = 2; 20 | STOPPED = 3; 21 | RUNNING = 4; 22 | TERMINATED = 5; 23 | } 24 | 25 | Team team = 16; 26 | } 27 | -------------------------------------------------------------------------------- /bench/benchmarker/scenario/postvalidation.go: -------------------------------------------------------------------------------- 1 | package scenario 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "time" 7 | 8 | "github.com/isucon/isucon14/bench/benchmarker/webapp" 9 | ) 10 | 11 | func PostValidation(ctx context.Context, target string, addr string) error { 12 | clientConfig := webapp.ClientConfig{ 13 | TargetBaseURL: target, 14 | TargetAddr: addr, 15 | ClientIdleConnTimeout: 10 * time.Second, 16 | } 17 | 18 | if err := validateInitialData(ctx, clientConfig); err != nil { 19 | slog.String("初期データのバリデーションに失敗", err.Error()) 20 | return err 21 | } 22 | 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /webapp/perl/cpanfile: -------------------------------------------------------------------------------- 1 | requires 'perl', '5.040'; 2 | 3 | requires 'Kossy', '0.63'; 4 | requires 'Starlet', '0.31'; 5 | requires 'HTTP::Status', '7.00'; 6 | requires 'Type::Tiny', '2.006000'; 7 | requires 'Cpanel::JSON::XS', '4.38'; 8 | requires 'Crypt::URandom','0.40'; 9 | requires 'Time::Moment'; 10 | requires 'Furl'; 11 | requires 'HTTP::Parser::XS'; 12 | requires 'Syntax::Keyword::Match'; 13 | 14 | requires 'Mojolicious'; 15 | requires 'Mojo::mysql'; 16 | 17 | # DB 18 | requires 'DBD::mysql', '5.010'; 19 | requires 'DBIx::Sunny', '0.9993'; 20 | 21 | requires 'HTTP::Parser::XS', '0.17'; 22 | 23 | requires 'Data::ULID::XS', '1.000' 24 | -------------------------------------------------------------------------------- /webapp/php/src/Result/ChairStats.php: -------------------------------------------------------------------------------- 1 | error !== null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/contest.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | message Contest { 8 | google.protobuf.Timestamp registration_opens_at = 1; 9 | google.protobuf.Timestamp registration_closes_at = 2; 10 | google.protobuf.Timestamp starts_at = 3; 11 | google.protobuf.Timestamp freezes_at = 4; 12 | google.protobuf.Timestamp ends_at = 5; 13 | 14 | enum Status { 15 | STANDBY = 0; 16 | REGISTRATION = 1; 17 | STARTED = 2; 18 | FINISHED = 3; 19 | } 20 | Status status = 6; 21 | bool frozen = 7; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/frame/frame.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC, PropsWithChildren } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export const MainFrame: FC>> = ({ 5 | children, 6 | className, 7 | ...props 8 | }) => { 9 | return ( 10 |
    17 |
    {children}
    18 |
    19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/app/components/hooks/use-on-click-outside.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useEffect } from "react"; 2 | 3 | export const useOnClickOutside = ( 4 | ref: RefObject, 5 | handler: (event: MouseEvent) => void, 6 | ) => { 7 | useEffect(() => { 8 | const handleOutsideClick = (e: MouseEvent) => { 9 | if (ref.current?.contains && !ref.current.contains(e.target as Node)) { 10 | handler(e); 11 | } 12 | }; 13 | 14 | document.addEventListener("click", handleOutsideClick); 15 | 16 | return () => { 17 | document.removeEventListener("click", handleOutsideClick); 18 | }; 19 | }, [ref, handler]); 20 | }; 21 | -------------------------------------------------------------------------------- /webapp/nodejs/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "space" 15 | }, 16 | "organizeImports": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true 23 | } 24 | }, 25 | "javascript": { 26 | "formatter": { 27 | "quoteStyle": "double" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/admin/dashboard.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.admin; 3 | 4 | 5 | import "isuxportal/resources/leaderboard.proto"; 6 | import "google/protobuf/timestamp.proto"; 7 | 8 | message DashboardQuery {} 9 | 10 | message DashboardResponse { 11 | isuxportal.proto.resources.Leaderboard leaderboard = 1; 12 | google.protobuf.Timestamp earliest_unanswered_clarification_at = 2; 13 | int64 unanswered_clarification_count = 3; 14 | } 15 | 16 | message SoloDashboardQuery {} 17 | 18 | message SoloDashboardResponse { 19 | isuxportal.proto.resources.LeaderboardItem leaderboard_item = 1; 20 | } 21 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/nginx/files/etc/nginx/tls/dummy.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBgDCCASWgAwIBAgIUS//zuVGNeQGhRBMLMMc8s8v6k94wCgYIKoZIzj0EAwIw 3 | FTETMBEGA1UEAwwKaXN1Y29uLm5ldDAeFw0yNDEyMDIxMjE3MTlaFw0yNTEyMDIx 4 | MjE3MTlaMBUxEzARBgNVBAMMCmlzdWNvbi5uZXQwWTATBgcqhkjOPQIBBggqhkjO 5 | PQMBBwNCAAQEiaEkT85s5L7NH8/JDgH2MeRf/xI2DBGvRFqKSBM3Kqw4T/b9lica 6 | nhyFdSHPzMKAvbHNZh9k8yjtzrXJoMdYo1MwUTAdBgNVHQ4EFgQUwGvfEaZiMTw3 7 | ohbKYXGmPg6ukNUwHwYDVR0jBBgwFoAUwGvfEaZiMTw3ohbKYXGmPg6ukNUwDwYD 8 | VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEAvAasAGSugjJ6sBIhCdwg 9 | MU6nf/H9Wg6Kc+NMHxtnTxICIQD0sJdApEjlva7V2LdV4EAHVc1+2yO9g1duOhSv 10 | NrBKnA== 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /provisioning/ansible/make_latest_files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | cd $(dirname $0) 5 | 6 | cd ../../frontend 7 | make 8 | cp -r ./build/client/ ../webapp/public/ 9 | cd ../provisioning/ansible 10 | 11 | cd ../../bench 12 | task build-linux-amd64 13 | mkdir -p ../provisioning/ansible/roles/bench/files 14 | mv bin/bench_linux_amd64 ../provisioning/ansible/roles/bench/files 15 | cd ../provisioning/ansible 16 | 17 | cd ../../envcheck/isucon-env-checker 18 | task build-linux-amd64 19 | cd ../../provisioning/ansible 20 | 21 | cd ../../ 22 | tar -zcvf webapp.tar.gz webapp 23 | mv webapp.tar.gz provisioning/ansible/roles/webapp/files 24 | 25 | cd ./provisioning/ansible 26 | -------------------------------------------------------------------------------- /docs/prh-rule.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - expected: サーバー 3 | patterns: 4 | - /サーバー?/g 5 | - expected: シミュレーター 6 | patterns: 7 | - /シ(ュミ|ミュ)レーター?/g 8 | - expected: シミュレート 9 | patterns: 10 | - /シ(ュミ|ミュ)レート/g 11 | - expected: AWSマネジメントコンソール 12 | patterns: 13 | - /AWSマネー?ジメントコンソール/g 14 | - /(? 10 | 11 | ISURIDE 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /bench/benchmarker/webapp/api/oas_interfaces_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by ogen, DO NOT EDIT. 2 | package api 3 | 4 | type AppPostPaymentMethodsRes interface { 5 | appPostPaymentMethodsRes() 6 | } 7 | 8 | type AppPostRideEvaluationRes interface { 9 | appPostRideEvaluationRes() 10 | } 11 | 12 | type AppPostRidesEstimatedFareRes interface { 13 | appPostRidesEstimatedFareRes() 14 | } 15 | 16 | type AppPostRidesRes interface { 17 | appPostRidesRes() 18 | } 19 | 20 | type AppPostUsersRes interface { 21 | appPostUsersRes() 22 | } 23 | 24 | type ChairPostRideStatusRes interface { 25 | chairPostRideStatusRes() 26 | } 27 | 28 | type OwnerPostOwnersRes interface { 29 | ownerPostOwnersRes() 30 | } 31 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/files/isuride-matcher.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isuride-matcher 3 | After=isuride-go.service 4 | After=isuride-node.service 5 | After=isuride-perl.service 6 | After=isuride-php.service 7 | After=isuride-python.service 8 | After=isuride-ruby.service 9 | After=isuride-rust.service 10 | 11 | [Service] 12 | User=isucon 13 | Group=isucon 14 | EnvironmentFile=/home/isucon/env.sh 15 | 16 | ExecStart=/bin/sh -c "while true; do curl -s https://isuride.xiv.isucon.net/api/internal/matching; sleep $ISUCON_MATCHING_INTERVAL; done" 17 | ExecStop=/bin/kill -s QUIT $MAINPID 18 | 19 | Restart=on-failure 20 | RestartSec=5 21 | 22 | [Install] 23 | WantedBy=multi-user.target 24 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "textlint *.md", 8 | "lint:fix": "textlint --fix *.md" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "textlint": "^14.4.0", 15 | "textlint-filter-rule-allowlist": "^4.0.0", 16 | "textlint-rule-no-todo": "^2.0.1", 17 | "textlint-rule-preset-ja-spacing": "^2.4.3", 18 | "textlint-rule-preset-ja-technical-writing": "^10.0.1", 19 | "textlint-rule-preset-jtf-style": "^3.0.0", 20 | "textlint-rule-prh": "^6.0.0" 21 | }, 22 | "packageManager": "pnpm@9.15.0" 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports": "always" 6 | }, 7 | "editor.formatOnSave": true, 8 | "[go]": { 9 | "editor.defaultFormatter": "golang.go" 10 | }, 11 | "[typescriptreact]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[typescript]": { 15 | "editor.defaultFormatter": "biomejs.biome" 16 | }, 17 | "[markdown]": { 18 | "editor.formatOnSave": false 19 | }, 20 | "perlnavigator.includePaths": [ 21 | "webapp/perl/lib", 22 | "webapp/perl/local/lib/perl5" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/tasks/node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Mkdir webapp for node 3 | become: true 4 | become_user: isucon 5 | ansible.builtin.file: 6 | path: /home/isucon/webapp/nodejs 7 | state: directory 8 | 9 | - name: Build isuride-node 10 | become: true 11 | become_user: isucon 12 | shell: | 13 | /home/isucon/.x npm install 14 | args: 15 | chdir: /home/isucon/webapp/nodejs 16 | 17 | - name: Put systemd service 18 | become: true 19 | ansible.builtin.copy: 20 | src: isuride-node.service 21 | dest: /etc/systemd/system/ 22 | 23 | - name: Start webapp 24 | become: true 25 | service: 26 | name: isuride-node 27 | enabled: false 28 | state: stopped 29 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/tasks/ruby.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Mkdir webapp for ruby 3 | become: true 4 | become_user: isucon 5 | ansible.builtin.file: 6 | path: /home/isucon/webapp/ruby 7 | state: directory 8 | 9 | - name: Build isuride-ruby 10 | become: true 11 | become_user: isucon 12 | shell: | 13 | /home/isucon/.x bundle install 14 | args: 15 | chdir: /home/isucon/webapp/ruby 16 | 17 | - name: Put systemd service 18 | become: true 19 | ansible.builtin.copy: 20 | src: isuride-ruby.service 21 | dest: /etc/systemd/system/ 22 | 23 | - name: Start webapp 24 | become: true 25 | service: 26 | name: isuride-ruby 27 | enabled: false 28 | state: stopped 29 | -------------------------------------------------------------------------------- /webapp/php/src/Database/Model/RetrievedAt.php: -------------------------------------------------------------------------------- 1 | retrievedAt); 24 | $dateTimeImmutable->setTimezone(new DateTimeZone('UTC')); 25 | return (int)$dateTimeImmutable->format('Uv'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /webapp/ruby/lib/isuride/initialize_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'open3' 4 | 5 | require 'isuride/base_handler' 6 | 7 | module Isuride 8 | class InitializeHandler < BaseHandler 9 | PostInitializeRequest = Data.define(:payment_server) 10 | 11 | post '/api/initialize' do 12 | req = bind_json(PostInitializeRequest) 13 | 14 | out, status = Open3.capture2e('../sql/init.sh') 15 | unless status.success? 16 | raise HttpError.new(500, "failed to initialize: #{out}") 17 | end 18 | 19 | db.xquery("UPDATE settings SET value = ? WHERE name = 'payment_gateway_url'", req.payment_server) 20 | 21 | json(language: 'ruby') 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/tasks/rust.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Mkdir webapp for rust 3 | become: true 4 | become_user: isucon 5 | ansible.builtin.file: 6 | path: /home/isucon/webapp/rust 7 | state: directory 8 | 9 | - name: Build isuride-rust 10 | become: true 11 | become_user: isucon 12 | shell: | 13 | /home/isucon/.x cargo build --release --locked 14 | args: 15 | chdir: /home/isucon/webapp/rust 16 | 17 | - name: Put systemd service 18 | become: true 19 | ansible.builtin.copy: 20 | src: isuride-rust.service 21 | dest: /etc/systemd/system/ 22 | 23 | - name: Start webapp 24 | become: true 25 | service: 26 | name: isuride-rust 27 | enabled: false 28 | state: stopped 29 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/team.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | import "isuxportal/resources/contestant.proto"; 6 | 7 | message Team { 8 | int64 id = 1; 9 | string name = 2; 10 | int64 leader_id = 3; 11 | repeated int64 member_ids = 4; 12 | bool final_participation = 5; 13 | bool hidden = 6; 14 | bool withdrawn = 7; 15 | bool disqualified = 9; 16 | 17 | message StudentStatus { bool status = 1; } 18 | StudentStatus student = 10; 19 | 20 | message TeamDetail { 21 | string email_address = 1; 22 | 23 | string invite_token = 16; 24 | } 25 | TeamDetail detail = 8; 26 | 27 | Contestant leader = 16; 28 | repeated Contestant members = 17; 29 | } 30 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/tasks/perl.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Mkdir webapp for perl 3 | become: true 4 | become_user: isucon 5 | ansible.builtin.file: 6 | path: /home/isucon/webapp/perl 7 | state: directory 8 | 9 | - name: Build isuride-perl 10 | become: true 11 | become_user: isucon 12 | shell: | 13 | /home/isucon/.x cpm install --show-build-log-on-failure 14 | args: 15 | chdir: /home/isucon/webapp/perl 16 | 17 | - name: Put systemd service 18 | become: true 19 | ansible.builtin.copy: 20 | src: isuride-perl.service 21 | dest: /etc/systemd/system/ 22 | 23 | - name: Start webapp 24 | become: true 25 | service: 26 | name: isuride-perl 27 | enabled: false 28 | state: stopped 29 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/tasks/go.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Mkdir webapp for go 3 | become: true 4 | become_user: isucon 5 | ansible.builtin.file: 6 | path: /home/isucon/webapp/go 7 | state: directory 8 | 9 | - name: Build isuride-go 10 | become: true 11 | become_user: isucon 12 | shell: | 13 | /home/isucon/local/golang/bin/go build -o /home/isucon/webapp/go/isuride -ldflags "-s -w" 14 | args: 15 | chdir: /home/isucon/webapp/go 16 | 17 | - name: Put systemd service 18 | become: true 19 | ansible.builtin.copy: 20 | src: isuride-go.service 21 | dest: /etc/systemd/system/ 22 | 23 | - name: Start webapp 24 | become: true 25 | service: 26 | name: isuride-go 27 | enabled: true 28 | state: restarted 29 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["env.d.ts", "**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 5 | "types": ["@remix-run/node", "vite/client"], 6 | "isolatedModules": true, 7 | "esModuleInterop": true, 8 | "jsx": "react-jsx", 9 | "module": "ESNext", 10 | "moduleResolution": "Bundler", 11 | "resolveJsonModule": true, 12 | "target": "ES2022", 13 | "strict": true, 14 | "allowJs": true, 15 | "skipLibCheck": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "~/*": ["./app/*"] 20 | }, 21 | 22 | // Remix takes care of building everything in `remix build`. 23 | "noEmit": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/common/me.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.common; 3 | 4 | 5 | import "isuxportal/resources/team.proto"; 6 | import "isuxportal/resources/contestant.proto"; 7 | import "isuxportal/resources/contestant_instance.proto"; 8 | import "isuxportal/resources/contest.proto"; 9 | 10 | message GetCurrentSessionRequest {} 11 | 12 | message GetCurrentSessionResponse { 13 | isuxportal.proto.resources.Team team = 1; 14 | isuxportal.proto.resources.Contestant contestant = 2; 15 | string discord_server_id = 3; 16 | 17 | isuxportal.proto.resources.Contest contest = 4; 18 | 19 | repeated isuxportal.proto.resources.ContestantInstance contestant_instances = 5; 20 | 21 | string push_vapid_key = 6; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # ISURIDE Frontend 2 | 3 | ## Stack 4 | - Remix 5 | - tailwind 6 | 7 | ## Routes 8 | 9 | ### Top 10 | - **/** 11 | - ランディングページ 12 | 13 | ### Client Application 14 | - **/client** 15 | - ISURIDE利用者用 モバイルクラインアント 16 | - **/client/history** 17 | - 履歴画面 18 | - **/client/user** 19 | - ユーザー画面 20 | - **/client/register** 21 | - ユーザー登録画面 22 | - **/client/login** 23 | - ユーザーログイン画面 24 | 25 | ### Owner Application 26 | - **/owner** 27 | - ISURIDEオーナー向け 管理アプリケーション 28 | - **/owner/sales** 29 | - 売上確認画面 30 | - **/owner/register** 31 | - オーナー登録画面 32 | - **/owner/login** 33 | - オーナーログイン画面 34 | 35 | ### Simulator 36 | - **/simulator** 37 | - ISUCON競技者用 アプリ動作シミュレーター 38 | 39 | ## Development 40 | 41 | ```sh 42 | pnpm install 43 | pnpm dev 44 | ``` 45 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/tasks/python.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Mkdir webapp for python 3 | become: true 4 | become_user: isucon 5 | ansible.builtin.file: 6 | path: /home/isucon/webapp/python 7 | state: directory 8 | 9 | # ここでPythonインタプリタもダウンロードされる 10 | - name: Build isuride-python 11 | become: true 12 | become_user: isucon 13 | shell: | 14 | /home/isucon/.local/bin/uv sync --locked --no-dev 15 | args: 16 | chdir: /home/isucon/webapp/python 17 | 18 | - name: Put systemd service 19 | become: true 20 | ansible.builtin.copy: 21 | src: isuride-python.service 22 | dest: /etc/systemd/system/ 23 | 24 | - name: Start webapp 25 | become: true 26 | service: 27 | name: isuride-python 28 | enabled: false 29 | state: stopped 30 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/contestant/benchmark.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.contestant; 3 | 4 | 5 | import "isuxportal/resources/benchmark_job.proto"; 6 | 7 | message ListBenchmarkJobsQuery { int64 limit = 1; } 8 | 9 | message ListBenchmarkJobsResponse { 10 | repeated isuxportal.proto.resources.BenchmarkJob jobs = 1; 11 | } 12 | 13 | message EnqueueBenchmarkJobRequest { 14 | // target ContestantInstance id 15 | int64 target_id = 1; 16 | } 17 | 18 | message EnqueueBenchmarkJobResponse { 19 | isuxportal.proto.resources.BenchmarkJob job = 1; 20 | } 21 | 22 | // Query parameter 23 | message GetBenchmarkJobQuery { int64 id = 1; } 24 | 25 | message GetBenchmarkJobResponse { 26 | isuxportal.proto.resources.BenchmarkJob job = 1; 27 | } 28 | -------------------------------------------------------------------------------- /bench/internal/random/source.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand/v2" 5 | "sync" 6 | ) 7 | 8 | type lockedSource struct { 9 | inner rand.Source 10 | sync.Mutex 11 | } 12 | 13 | func (r *lockedSource) Uint64() uint64 { 14 | r.Lock() 15 | defer r.Unlock() 16 | return r.inner.Uint64() 17 | } 18 | 19 | func NewLockedSource(src rand.Source) rand.Source { 20 | return &lockedSource{ 21 | inner: src, 22 | } 23 | } 24 | 25 | func NewLockedRand(src rand.Source) *rand.Rand { 26 | return rand.New(NewLockedSource(src)) 27 | } 28 | 29 | func CreateChildSource(parent rand.Source) rand.Source { 30 | return rand.NewPCG(parent.Uint64(), parent.Uint64()) 31 | } 32 | 33 | func CreateChildRand(parent rand.Source) *rand.Rand { 34 | return NewLockedRand(CreateChildSource(parent)) 35 | } 36 | -------------------------------------------------------------------------------- /frontend/app/components/icon/pin.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | 3 | export const PinIcon: FC<{ color?: string } & ComponentProps<"svg">> = ({ 4 | color, 5 | ...props 6 | }) => { 7 | return ( 8 | 9 | 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/smartphone/smartphone.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function SmartPhone({ children }: PropsWithChildren) { 5 | return ( 6 |
    13 |
    14 | {children} 15 |
    16 |
    17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /bench/internal/concurrent/simple_set.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import "iter" 4 | 5 | type SimpleSet[K comparable] struct { 6 | m *SimpleMap[K, struct{}] 7 | } 8 | 9 | func NewSimpleSet[K comparable]() *SimpleSet[K] { 10 | return &SimpleSet[K]{m: NewSimpleMap[K, struct{}]()} 11 | } 12 | 13 | func (s *SimpleSet[K]) Has(key K) bool { 14 | _, ok := s.m.Get(key) 15 | return ok 16 | } 17 | 18 | func (s *SimpleSet[K]) Add(key K) { 19 | s.m.Set(key, struct{}{}) 20 | } 21 | 22 | func (s *SimpleSet[K]) Delete(key K) { 23 | s.m.Delete(key) 24 | } 25 | 26 | func (s *SimpleSet[K]) Len() int { 27 | return s.m.Len() 28 | } 29 | 30 | func (s *SimpleSet[K]) Iter() iter.Seq[K] { 31 | return func(yield func(K) bool) { 32 | for k, _ := range s.m.Iter() { 33 | if !yield(k) { 34 | break 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/app/components/icon/human.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | import colors from "tailwindcss/colors"; 3 | 4 | export const HumanIcon: FC> = (props) => { 5 | return ( 6 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | // https://github.com/google/material-design-icons/blob/master/LICENSE 20 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/tasks/payment_mock.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Mkdir webapp for payment_mock 3 | become: true 4 | become_user: isucon 5 | ansible.builtin.file: 6 | path: /home/isucon/webapp/ 7 | state: directory 8 | 9 | - name: Build payment_mock 10 | become: true 11 | become_user: isucon 12 | shell: | 13 | /home/isucon/local/golang/bin/go build -o /home/isucon/webapp/payment_mock/payment_mock -ldflags "-s -w" 14 | args: 15 | chdir: /home/isucon/webapp/payment_mock 16 | 17 | - name: Put systemd service 18 | become: true 19 | ansible.builtin.copy: 20 | src: isuride-payment_mock.service 21 | dest: /etc/systemd/system/ 22 | 23 | - name: Start payment_mock 24 | become: true 25 | service: 26 | name: isuride-payment_mock 27 | enabled: true 28 | state: restarted 29 | -------------------------------------------------------------------------------- /webapp/perl/lib/Kossy/Isuride/Models.pm: -------------------------------------------------------------------------------- 1 | package Kossy::Isuride::Models; 2 | use v5.40; 3 | use utf8; 4 | 5 | use Types::Standard -types; 6 | use Cpanel::JSON::XS::Type qw(JSON_TYPE_STRING JSON_TYPE_INT JSON_TYPE_STRING_OR_NULL json_type_arrayof); 7 | 8 | use Exporter 'import'; 9 | 10 | our @EXPORT_OK = qw( 11 | Coordinate 12 | ); 13 | 14 | use constant ChairModel => { 15 | name => Str, 16 | speed => Int, 17 | }; 18 | 19 | use constant Chair => { 20 | id => Str, 21 | owner_id => Str, 22 | name => Str, 23 | model => Str, 24 | is_active => Bool, 25 | access_token => Str, 26 | created_at => Int, 27 | updated_at => Int, 28 | }; 29 | 30 | use constant Coordinate => { 31 | latitude => JSON_TYPE_INT, 32 | longitude => JSON_TYPE_INT, 33 | }; 34 | -------------------------------------------------------------------------------- /webapp/perl/lib/Mojo/Isuride/Models.pm: -------------------------------------------------------------------------------- 1 | package Mojo::Isuride::Models; 2 | use v5.40; 3 | use utf8; 4 | 5 | use Types::Standard -types; 6 | use Cpanel::JSON::XS::Type qw(JSON_TYPE_STRING JSON_TYPE_INT JSON_TYPE_STRING_OR_NULL json_type_arrayof); 7 | 8 | use Exporter 'import'; 9 | 10 | our @EXPORT_OK = qw( 11 | Coordinate 12 | ); 13 | 14 | use constant ChairModel => { 15 | name => Str, 16 | speed => Int, 17 | }; 18 | 19 | use constant Chair => { 20 | id => Str, 21 | owner_id => Str, 22 | name => Str, 23 | model => Str, 24 | is_active => Bool, 25 | access_token => Str, 26 | created_at => Int, 27 | updated_at => Int, 28 | }; 29 | 30 | use constant Coordinate => { 31 | latitude => JSON_TYPE_INT, 32 | longitude => JSON_TYPE_INT, 33 | }; 34 | -------------------------------------------------------------------------------- /docs/.textlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "preset-ja-technical-writing": { 4 | "sentence-length": { 5 | "max": 200 6 | }, 7 | "max-comma": { 8 | "max": 5 9 | }, 10 | "ja-no-redundant-expression": false, 11 | "no-doubled-joshi": false, 12 | "no-doubled-conjunction": false, 13 | "no-exclamation-question-mark": false, 14 | "no-mix-dearu-desumasu": { 15 | "strict": true, 16 | "preferInHeader": "ですます", 17 | "preferInBody": "ですます", 18 | "preferInList": "ですます" 19 | } 20 | }, 21 | "no-todo": true, 22 | "prh": { 23 | "rulePaths": ["prh-rule.yaml"] 24 | }, 25 | "preset-jtf-style": { 26 | "1.1.3.箇条書き": false, 27 | "3.1.1.全角文字と半角文字の間": false 28 | } 29 | }, 30 | "filters": { 31 | "allowlist": { 32 | "allow": ["負荷走行実行時", "AWS のインスタンス起動 API"] 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/app/components/icon/schedule.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | import colors from "tailwindcss/colors"; 3 | 4 | export const ScheduleIcon: FC> = (props) => { 5 | return ( 6 | 14 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /provisioning/packer/Makefile: -------------------------------------------------------------------------------- 1 | export BRANCH := $(shell git branch --contains | fgrep '*' | cut -d ' ' -f 2) 2 | .DEFAULT_GOAL := all 3 | 4 | .PHONY: all 5 | all: 6 | make echo-commit_hash 7 | make app-build 8 | make init 9 | make build 10 | 11 | .PHONY: base-build 12 | base-build: 13 | make echo-commit_hash 14 | packer init -upgrade isucon14_base_image.pkr.hcl 15 | packer build -var "commit_hash=$$(git rev-parse HEAD)" isucon14_base_image.pkr.hcl 16 | 17 | .PHONY: echo-commit_hash 18 | echo-commit_hash: 19 | git rev-parse HEAD 20 | 21 | .PHONY: app-build 22 | app-build: 23 | cd ../../provisioning/ansible/ && ./make_latest_files.sh 24 | 25 | .PHONY: init 26 | init: 27 | packer init -upgrade isucon14.pkr.hcl 28 | 29 | .PHONY: build 30 | build: 31 | packer build -var "commit_hash=$$(git rev-parse HEAD)" isucon14.pkr.hcl 32 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/header/header.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@remix-run/react"; 2 | import type { PropsWithChildren, FC } from "react"; 3 | import { ComponentProps } from "react"; 4 | import { twMerge } from "tailwind-merge"; 5 | 6 | export const Header: FC< 7 | PropsWithChildren<{ backTo?: `/${string}` } & ComponentProps<"header">> 8 | > = ({ backTo, children, className, ...props }) => { 9 | return ( 10 |
    18 | {backTo && ( 19 | 20 | 戻る 21 | 22 | )} 23 | {children} 24 |
    25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /webapp/php/src/Database/Model/RideRequest.php: -------------------------------------------------------------------------------- 1 | > = (props) => { 5 | return ( 6 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | // https://github.com/google/material-design-icons/blob/master/LICENSE 20 | -------------------------------------------------------------------------------- /bench/internal/concurrent/wait_group.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | ) 7 | 8 | // WaitChan wg.Waitが完了するまでブロックするチャネルを作成する 9 | func WaitChan(wg *sync.WaitGroup) <-chan struct{} { 10 | c := make(chan struct{}) 11 | go func() { 12 | defer close(c) 13 | wg.Wait() 14 | }() 15 | return c 16 | } 17 | 18 | // WaitGroupWithCount カウンタ付きsync.WaitGroup 19 | type WaitGroupWithCount struct { 20 | sync.WaitGroup 21 | count int64 22 | } 23 | 24 | func (wg *WaitGroupWithCount) Add(delta int) { 25 | atomic.AddInt64(&wg.count, int64(delta)) 26 | wg.WaitGroup.Add(delta) 27 | } 28 | 29 | func (wg *WaitGroupWithCount) Done() { 30 | atomic.AddInt64(&wg.count, -1) 31 | wg.WaitGroup.Done() 32 | } 33 | 34 | // Count Doneになっていない数を返す 35 | func (wg *WaitGroupWithCount) Count() int { 36 | return int(atomic.LoadInt64(&wg.count)) 37 | } 38 | -------------------------------------------------------------------------------- /envcheck/isucon-env-checker/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | p, err := LoadPortalCredentials() 11 | if err != nil { 12 | fmt.Println("チェッカーの設定ファイルが読み込めませんでした") 13 | os.Exit(1) 14 | } 15 | 16 | info, err := p.GetInfo("contest") 17 | if err != nil { 18 | fmt.Printf("ポータルから情報の取得に失敗しました: %v\n", err) 19 | os.Exit(1) 20 | } 21 | 22 | fmt.Println("環境をチェックしています...") 23 | result, err := Check(context.Background(), CheckConfig{ 24 | AMI: info.AMI, 25 | AZ: info.AZ, 26 | }) 27 | if err != nil { 28 | fmt.Printf("環境チェックに失敗しました: %v\n", err) 29 | os.Exit(1) 30 | } 31 | 32 | if err := p.SendResult(result); err != nil { 33 | fmt.Printf("チェック結果の送信に失敗しました: %v\n", err) 34 | os.Exit(1) 35 | } 36 | fmt.Println(result.Message) 37 | if !result.Passed { 38 | os.Exit(1) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /frontend/app/components/modules/price-text/price-text.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps, FC } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { Text } from "~/components/primitives/text/text"; 4 | 5 | type PriceTextProps = { 6 | value: number; 7 | } & ComponentProps<"span">; 8 | 9 | const formatter = new Intl.NumberFormat("ja-JP"); 10 | 11 | export const PriceText: FC = ({ 12 | value, 13 | className, 14 | ...props 15 | }) => { 16 | return ( 17 | 21 | 22 | {formatter.format(value)} 23 | 24 | 25 | 円 26 | 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /.github/disabled-workflows/frontend.yml: -------------------------------------------------------------------------------- 1 | name: Front-end CI 2 | on: 3 | push: 4 | paths: 5 | - .github/workflows/frontend.yml 6 | - frontend/**/* 7 | defaults: 8 | run: 9 | working-directory: ./frontend 10 | jobs: 11 | build: 12 | name: lint 13 | runs-on: codebuild-problem-github-actions-${{ github.run_id }}-${{ github.run_attempt }} 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: pnpm/action-setup@v4 17 | name: Install pnpm 18 | with: 19 | version: 9 20 | run_install: false 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: 20 24 | cache: pnpm 25 | cache-dependency-path: "frontend/pnpm-lock.yaml" 26 | - run: pnpm install 27 | - run: pnpm run lint 28 | - run: pnpm run fmtcheck 29 | - run: pnpm run typecheck 30 | -------------------------------------------------------------------------------- /frontend/app/components/icon/history.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | import colors from "tailwindcss/colors"; 3 | 4 | export const HistoryIcon: FC> = (props) => { 5 | return ( 6 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | // https://github.com/google/material-design-icons/blob/master/LICENSE 20 | -------------------------------------------------------------------------------- /frontend/app/components/icon/mobile.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | import colors from "tailwindcss/colors"; 3 | 4 | export const MobileIcon: FC> = (props) => { 5 | return ( 6 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | // https://github.com/google/material-design-icons/blob/master/LICENSE 20 | -------------------------------------------------------------------------------- /frontend/app/components/modules/modal-header/moda-header.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC, PropsWithChildren } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { Text } from "~/components/primitives/text/text"; 4 | 5 | export const ModalHeader: FC< 6 | { title: string; subTitle: string } & PropsWithChildren> 7 | > = ({ title, subTitle, className, children, ...props }) => { 8 | return ( 9 |
    16 | {children} 17 | 18 | {title} 19 | 20 | 21 | {subTitle} 22 | 23 |
    24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /development/compose-local.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: mysql:8 4 | environment: 5 | ENV: local-dev 6 | MYSQL_ROOT_PASSWORD: isucon 7 | ports: 8 | - 3306:3306 9 | volumes: 10 | - ../webapp/sql:/docker-entrypoint-initdb.d 11 | healthcheck: 12 | test: 13 | ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uisucon", "-pisucon"] 14 | interval: 5s 15 | timeout: 5s 16 | retries: 10 17 | start_period: 30s 18 | waiter: 19 | image: busybox 20 | depends_on: 21 | db: 22 | condition: service_healthy 23 | paymentmock: 24 | build: 25 | context: ../webapp/payment_mock 26 | ports: 27 | - 12345:12345 28 | matcher: 29 | image: curlimages/curl:latest 30 | command: /bin/sh -c "while true; do curl -s http://host.docker.internal:8080/api/internal/matching; sleep 0.5; done" 31 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/rating/rating.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { RatingStar } from "~/components/icon/rating-star"; 4 | 5 | type RatingProps = ComponentProps<"div"> & { 6 | size?: number; 7 | rating: number; 8 | }; 9 | 10 | export const Rating: FC = ({ 11 | size = 40, 12 | rating, 13 | className, 14 | ...props 15 | }) => { 16 | return ( 17 |
    18 | {Array.from({ length: 5 }).map((_, index) => { 19 | const starValue = index + 1; 20 | return ( 21 | 27 | ); 28 | })} 29 |
    30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /bench/benchmarker/webapp/api/oas_parameters_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by ogen, DO NOT EDIT. 2 | 3 | package api 4 | 5 | // AppGetNearbyChairsParams is parameters of app-get-nearby-chairs operation. 6 | type AppGetNearbyChairsParams struct { 7 | // 緯度. 8 | Latitude int 9 | // 経度. 10 | Longitude int 11 | // 検索距離. 12 | Distance OptInt 13 | } 14 | 15 | // AppPostRideEvaluationParams is parameters of app-post-ride-evaluation operation. 16 | type AppPostRideEvaluationParams struct { 17 | // ライドID. 18 | RideID string 19 | } 20 | 21 | // ChairPostRideStatusParams is parameters of chair-post-ride-status operation. 22 | type ChairPostRideStatusParams struct { 23 | // ライドID. 24 | RideID string 25 | } 26 | 27 | // OwnerGetSalesParams is parameters of owner-get-sales operation. 28 | type OwnerGetSalesParams struct { 29 | // 開始日時(含む). 30 | Since OptInt64 31 | // 終了日時(含む). 32 | Until OptInt64 33 | } 34 | -------------------------------------------------------------------------------- /browsercheck/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | playwright-chromium: 12 | specifier: ^1.49.0 13 | version: 1.49.0 14 | 15 | packages: 16 | 17 | playwright-chromium@1.49.0: 18 | resolution: {integrity: sha512-xU+nOHawNFKfJsHTTGyWqSJ5nRGGHQq1wTsc49H9rM+hDNnoKZi+3m12mGoLpqvJP7vRjZQ3uvU9/UJZbrJ1AA==} 19 | engines: {node: '>=18'} 20 | hasBin: true 21 | 22 | playwright-core@1.49.0: 23 | resolution: {integrity: sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==} 24 | engines: {node: '>=18'} 25 | hasBin: true 26 | 27 | snapshots: 28 | 29 | playwright-chromium@1.49.0: 30 | dependencies: 31 | playwright-core: 1.49.0 32 | 33 | playwright-core@1.49.0: {} 34 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/benchmark_result.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | import "isuxportal/resources/survey_response.proto"; 7 | 8 | message BenchmarkResult { 9 | bool finished = 1; 10 | bool passed = 2; 11 | int64 score = 3; 12 | ScoreBreakdown score_breakdown = 4; 13 | message ScoreBreakdown { 14 | int64 raw = 1; 15 | int64 deduction = 2; 16 | } 17 | 18 | // only present for finished result 19 | Execution execution = 5; 20 | message Execution { 21 | string reason = 1; 22 | string stdout = 2; 23 | string stderr = 3; 24 | int32 exit_status = 4; 25 | int32 exit_signal = 5; 26 | bool signaled = 6; 27 | } 28 | 29 | google.protobuf.Timestamp marked_at = 6; 30 | 31 | // TODO: not available in responses 32 | SurveyResponse survey_response = 8; 33 | } 34 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/admin/leaderboard_dump.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.admin; 3 | 4 | 5 | import "isuxportal/resources/contestant_instance.proto"; 6 | import "isuxportal/resources/leaderboard.proto"; 7 | import "isuxportal/resources/team.proto"; 8 | import "google/protobuf/timestamp.proto"; 9 | 10 | message GetLeaderboardDumpQuery { 11 | string when = 1; // ISO8601 or "contest-end" 12 | } 13 | 14 | message GetLeaderboardDumpResponse { 15 | repeated LeaderboardDumpItem items = 1; 16 | 17 | message LeaderboardDumpItem { 18 | int64 position = 1; 19 | isuxportal.proto.resources.Team team = 2; 20 | isuxportal.proto.resources.LeaderboardItem.LeaderboardScore best_score = 3; 21 | isuxportal.proto.resources.LeaderboardItem.LeaderboardScore latest_score = 4; 22 | isuxportal.proto.resources.ContestantInstance target = 5; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/app/components/icon/rating-star.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps, FC } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export const RatingStar: FC & { rated: boolean }> = ({ 5 | rated, 6 | className, 7 | ...props 8 | }) => { 9 | return ( 10 | 21 | 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /development/dockerfiles/Dockerfile.php: -------------------------------------------------------------------------------- 1 | FROM php:8.3.13-fpm-bullseye 2 | 3 | WORKDIR /tmp 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | 6 | RUN apt-get clean \ 7 | && apt-get update \ 8 | && apt-get install -y locales locales-all default-mysql-client git \ 9 | && apt-get clean \ 10 | && rm -rf /var/lib/apt/lists/* 11 | 12 | RUN docker-php-ext-install pdo_mysql 13 | #RUN docker-php-ext-install opcache 14 | 15 | RUN locale-gen en_US.UTF-8 16 | RUN useradd --uid=1001 --create-home isucon 17 | USER isucon 18 | 19 | WORKDIR /home/isucon/webapp/php 20 | COPY --chown=isucon:isucon ./ /home/isucon/webapp/php/ 21 | 22 | COPY --from=composer:latest /usr/bin/composer /usr/bin/composer 23 | 24 | RUN /usr/bin/composer install --no-dev --no-interaction --no-progress --no-suggest 25 | ENV COMPOSER_ALLOW_SUPERUSER=1 26 | 27 | ENV LANG en_US.UTF-8 28 | ENV LANGUAGE en_US:en 29 | ENV LC_ALL en_US.UTF-8 30 | 31 | ENV TZ utc 32 | -------------------------------------------------------------------------------- /browsercheck/config.js: -------------------------------------------------------------------------------- 1 | export const baseUrl = "https://isuride.xiv.isucon.net"; 2 | export const randomString = Math.floor(Math.random() * 36 ** 6).toString(36); 3 | 4 | /** @type {Array<{ path: string, selector: string }>} */ 5 | export const pages = [ 6 | // "/client/register", // 別処理 7 | // "/client/register-payment", // 別処理 8 | // "/owner/register", // 別処理 9 | // "/owner/login", // 別処理 10 | { path: "/client", selector: "nav" }, 11 | { path: "/client/history", selector: "nav" }, 12 | { path: "/owner", selector: "table" }, 13 | { path: "/owner/sales", selector: "table" }, 14 | ]; 15 | 16 | // format: "teamId\tip" 17 | const raw_teams = ` 18 | 14 57.182.82.181 19 | `; 20 | 21 | /** @type {Array<{teamId: number, ip: string}>} */ 22 | export const teams = raw_teams 23 | .trim() 24 | .split("\n") 25 | .map(line => { 26 | const [teamId, ip] = line.split("\t"); 27 | return { teamId: Number(teamId), ip }; 28 | }); 29 | -------------------------------------------------------------------------------- /webapp/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "isuride" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | anyhow = { version = "1", features = ["backtrace"] } 9 | axum = "0.7" 10 | axum-extra = { version = "0.9", features = ["cookie"] } 11 | chrono = "0.4" 12 | hex = "0.4" 13 | listenfd = "1" 14 | num-traits = "0.2" 15 | rand = "0.8" 16 | reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "http2", "json"] } 17 | serde = { version = "1", features = ["derive"] } 18 | sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio-rustls", "mysql", "macros", "chrono", "rust_decimal"] } 19 | thiserror = "2" 20 | tokio = { version = "1", features = ["macros", "rt-multi-thread", "net", "process"] } 21 | tower-http = { version = "0.6", features = ["trace"] } 22 | tracing = "0.1" 23 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 24 | ulid = "1" 25 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | init: 5 | cmds: 6 | - go install github.com/sqldef/sqldef/cmd/mysqldef@latest 7 | up: 8 | cmds: 9 | - docker compose -f compose-local.yml up -d 10 | dir: ./development 11 | down: 12 | cmds: 13 | - docker compose -f compose-local.yml down -v --remove-orphans 14 | dir: ./development 15 | migrate: 16 | deps: 17 | - up 18 | cmds: 19 | - sed /^USE\ isuride\;/d < webapp/sql/1-schema.sql | mysqldef -p isucon isuride 20 | sources: 21 | - webapp/sql/1-schema.sql 22 | go:build: 23 | cmd: go build -o isuride{{exeExt}} . 24 | dir: webapp/go 25 | sources: 26 | - ./*.go 27 | - go.mod 28 | - go.sum 29 | go:run: 30 | deps: 31 | - go:build 32 | cmd: ./isuride{{exeExt}} 33 | dir: webapp/go 34 | perl:run: 35 | cmd: carton exec plackup -s Starlet -p 8080 -Ilib -r app.psgi 36 | dir: webapp/perl 37 | -------------------------------------------------------------------------------- /bench/benchmarker/webapp/client_static.go: -------------------------------------------------------------------------------- 1 | package webapp 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/isucon/isucon14/bench/benchrun" 9 | ) 10 | 11 | func (c *Client) StaticGetFileHash(ctx context.Context, path string) (string, error) { 12 | req, err := c.agent.NewRequest(http.MethodGet, path, nil) 13 | if err != nil { 14 | return "", err 15 | } 16 | 17 | resp, err := c.agent.Do(ctx, req) 18 | if err != nil { 19 | return "", fmt.Errorf("GET %sのリクエストが失敗しました: %w", path, err) 20 | } 21 | defer closeBody(resp) 22 | 23 | if resp.StatusCode < 200 || resp.StatusCode >= 400 { 24 | return "", fmt.Errorf("GET %sへのリクエストに対して、期待されたHTTPステータスコードが確認できませんでした (expected:200~399, actual:%d)", path, resp.StatusCode) 25 | } 26 | 27 | hash, err := benchrun.GetHashFromStream(resp.Body) 28 | if err != nil { 29 | return "", fmt.Errorf("GET %sのレスポンスのボディの取得に失敗しました: %w", path, err) 30 | } 31 | 32 | return hash, nil 33 | } 34 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/notification.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 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 { 12 | int64 benchmark_job_id = 1; 13 | } 14 | message ClarificationMessage { 15 | int64 clarification_id = 1; 16 | bool owned = 2; // True when a clarification is sent from a team of notification recipient 17 | bool updated = 3; // True when a clarification was answered and have updated 18 | bool admin = 4; // True when a clarification was opened by admin 19 | } 20 | message TestMessage { 21 | int64 something = 1; 22 | } 23 | oneof content { 24 | BenchmarkJobMessage content_benchmark_job = 3; 25 | ClarificationMessage content_clarification = 4; 26 | TestMessage content_test = 5; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/contestant/notifications.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.contestant; 3 | 4 | 5 | import "isuxportal/resources/notification.proto"; 6 | 7 | message ListNotificationsQuery { 8 | // Last notifications.id that a user-agent has received through ListNotificationsQuery during a current session. 9 | // If not specified (=0), uses server-side `read` column as a hint. 10 | int64 after = 1; 11 | } 12 | 13 | message ListNotificationsResponse { 14 | int64 last_answered_clarification_id = 1; 15 | repeated isuxportal.proto.resources.Notification notifications = 2; 16 | } 17 | 18 | message SubscribeNotificationRequest { 19 | string endpoint = 1; 20 | string p256dh = 2; 21 | string auth = 3; 22 | } 23 | 24 | message SubscribeNotificationResponse { 25 | } 26 | 27 | message UnsubscribeNotificationRequest { 28 | string endpoint = 1; 29 | } 30 | 31 | message UnsubscribeNotificationResponse { 32 | } 33 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/form/date-input.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps, FC, PropsWithoutRef } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | type DateInputProps = PropsWithoutRef<{ 5 | id: string; 6 | name: string; 7 | label?: string; 8 | className?: string; 9 | }> & 10 | ComponentProps<"input">; 11 | 12 | export const DateInput: FC = ({ 13 | label, 14 | id, 15 | className, 16 | ...props 17 | }) => { 18 | return ( 19 | <> 20 | {label ? ( 21 | 24 | ) : null} 25 | 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/leaderboard.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | import "isuxportal/resources/team.proto"; 7 | import "isuxportal/resources/contest.proto"; 8 | 9 | message LeaderboardItem { 10 | message LeaderboardScore { 11 | int64 score = 1; 12 | google.protobuf.Timestamp started_at = 2; 13 | google.protobuf.Timestamp marked_at = 3; 14 | } 15 | 16 | message History { 17 | repeated LeaderboardScore scores = 1; 18 | } 19 | 20 | LeaderboardScore best_score = 2; 21 | LeaderboardScore latest_score = 3; 22 | Team team = 16; 23 | 24 | History score_history = 17; 25 | } 26 | 27 | message Leaderboard { 28 | repeated LeaderboardItem teams = 1; 29 | repeated LeaderboardItem hidden_teams = 7; 30 | repeated LeaderboardItem progresses = 4; 31 | google.protobuf.Timestamp generated_at = 6; 32 | 33 | Contest contest = 5; 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /frontend/app/components/icon/desktop.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | import colors from "tailwindcss/colors"; 3 | 4 | export const DesktopIcon: FC> = (props) => { 5 | return ( 6 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | // https://github.com/google/material-design-icons/blob/master/LICENSE 20 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/bench/reporting.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.bench; 3 | 4 | 5 | import "isuxportal/resources/benchmark_result.proto"; 6 | 7 | service BenchmarkReport { 8 | rpc ReportBenchmarkResult(stream ReportBenchmarkResultRequest) 9 | returns (stream ReportBenchmarkResultResponse); 10 | rpc CompleteBenchmarkJob(CompleteBenchmarkJobRequest) 11 | returns (CompleteBenchmarkJobResponse); 12 | } 13 | 14 | message ReportBenchmarkResultRequest { 15 | int64 job_id = 1; 16 | string handle = 2; 17 | int64 nonce = 3 [ deprecated = true ]; 18 | isuxportal.proto.resources.BenchmarkResult result = 4; 19 | } 20 | 21 | message ReportBenchmarkResultResponse { 22 | int64 acked_nonce = 1 [ deprecated = true ]; 23 | } 24 | 25 | message CompleteBenchmarkJobRequest { 26 | int64 job_id = 1; 27 | string handle = 2; 28 | isuxportal.proto.resources.BenchmarkResult result = 4; 29 | } 30 | 31 | message CompleteBenchmarkJobResponse {} 32 | -------------------------------------------------------------------------------- /bench/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG SV_IMAGE="703671906592.dkr.ecr.ap-northeast-1.amazonaws.com/prod/benchmarker:supervisor" 2 | FROM ${SV_IMAGE} AS supervisor 3 | 4 | FROM public.ecr.aws/docker/library/golang:bookworm AS builder 5 | 6 | WORKDIR /app 7 | COPY . . 8 | RUN go build -o /app/bench -ldflags "-s -w" -trimpath . 9 | 10 | FROM public.ecr.aws/docker/library/debian:bookworm-slim 11 | 12 | RUN apt-get update 13 | RUN apt install -y ca-certificates openssl curl 14 | RUN bash -c "curl -sSfL https://raw.githubusercontent.com/aquaproj/aqua-installer/v3.1.0/aqua-installer | bash" 15 | ENV PATH=/root/.local/share/aquaproj-aqua/bin:$PATH 16 | ENV AQUA_GLOBAL_CONFIG=/etc/aqua/aqua.yaml 17 | COPY aqua.yaml /etc/aqua/ 18 | RUN aqua i -a 19 | WORKDIR /app 20 | COPY --from=builder /app/bench /app/bench 21 | COPY --from=supervisor /usr/local/bin/isuxportal-supervisor /app/supervisor 22 | COPY entrypoint.sh /usr/local/bin/entrypoint.sh 23 | ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] 24 | CMD ["/app/supervisor", "/app/bench", "run"] 25 | -------------------------------------------------------------------------------- /bench/Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | run-local: 5 | cmds: 6 | - go run . run --target http://localhost:8080 -t 60 {{.CLI_ARGS}} 7 | build: 8 | cmds: 9 | - go build -ldflags "-s -w" 10 | build-linux-amd64: 11 | cmds: 12 | - GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o ./bin/bench_linux_amd64 13 | build-image: 14 | cmds: 15 | - docker build -t isucon14-benchmarker . 16 | test: 17 | cmds: 18 | - go test ./... 19 | gen: 20 | cmds: 21 | - go generate ./... 22 | gen-init-data-sql: 23 | cmds: 24 | - go run . generate-init-data 25 | - mysqldump --skip-create-options --skip-add-drop-table --disable-keys --no-create-info --no-tablespaces -h 127.0.0.1 -u isucon -pisucon --databases isuride -n --ignore-table=isuride.settings --ignore-table=isuride.chair_models | gzip > ../webapp/sql/3-initial-data.sql.gz 26 | gen-frontend: 27 | dir: ../frontend 28 | cmds: 29 | - pnpm i 30 | - pnpm run build 31 | -------------------------------------------------------------------------------- /bench/benchrun/env.go: -------------------------------------------------------------------------------- 1 | package benchrun 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "strconv" 7 | ) 8 | 9 | // GetTargetAddress returns a target address given by a supervisor ($ISUXBENCH_TARGET environment variable.) 10 | func GetTargetAddress() string { 11 | return os.Getenv("ISUXBENCH_TARGET") 12 | } 13 | 14 | // GetAllAddresses returns all addresses given by a supervisor ($ISUXBENCH_ALL_ADDRESSES environment variable.) 15 | func GetAllAddresses() string { 16 | return os.Getenv("ISUXBENCH_ALL_ADDRESSES") 17 | } 18 | 19 | // GetReportFD returns a file descriptor for reporting results given by a supervisor ($ISUXBENCH_REPORT_FD environment variable.) 20 | func GetReportFD() (int, error) { 21 | defer os.Unsetenv("ISUXBENCH_REPORT_FD") 22 | 23 | strFD := os.Getenv("ISUXBENCH_REPORT_FD") 24 | if len(strFD) == 0 { 25 | return 0, errors.New("ISUXBENCH_REPORT_FD is not set") 26 | } 27 | 28 | fd, err := strconv.Atoi(strFD) 29 | if err != nil { 30 | return 0, err 31 | } 32 | 33 | return fd, nil 34 | } 35 | -------------------------------------------------------------------------------- /frontend/app/components/icon/isuride.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | 3 | export const IsurideIcon: FC> = (props) => { 4 | return ( 5 | 12 | 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/bench/receiving.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.bench; 3 | 4 | 5 | service BenchmarkQueue { 6 | rpc ReceiveBenchmarkJob(ReceiveBenchmarkJobRequest) 7 | returns (ReceiveBenchmarkJobResponse); 8 | 9 | rpc CancelOwnedBenchmarkJob(CancelOwnedBenchmarkJobRequest) 10 | returns (CancelOwnedBenchmarkJobResponse); 11 | } 12 | 13 | message ReceiveBenchmarkJobRequest { 14 | string token = 1; 15 | string instance_name = 2; 16 | int64 team_id = 3; 17 | } 18 | 19 | message ReceiveBenchmarkJobResponse { 20 | message JobHandle { 21 | int64 job_id = 1; 22 | string handle = 2; 23 | string target_ipv4_address = 3; 24 | string description_human = 4; 25 | repeated string all_ipv4_addresses = 5; 26 | } 27 | // optional 28 | JobHandle job_handle = 1; 29 | } 30 | 31 | message CancelOwnedBenchmarkJobRequest { 32 | string token = 1; 33 | string instance_name = 2; 34 | } 35 | 36 | message CancelOwnedBenchmarkJobResponse {} 37 | -------------------------------------------------------------------------------- /webapp/sql/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | cd $(dirname $0) 5 | 6 | if [ "${ENV:-}" == "local-dev" ]; then 7 | exit 0 8 | fi 9 | 10 | if test -f /home/isucon/env.sh; then 11 | . /home/isucon/env.sh 12 | fi 13 | 14 | ISUCON_DB_HOST=${ISUCON_DB_HOST:-127.0.0.1} 15 | ISUCON_DB_PORT=${ISUCON_DB_PORT:-3306} 16 | ISUCON_DB_USER=${ISUCON_DB_USER:-isucon} 17 | ISUCON_DB_PASSWORD=${ISUCON_DB_PASSWORD:-isucon} 18 | ISUCON_DB_NAME=${ISUCON_DB_NAME:-isuride} 19 | 20 | # MySQLを初期化 21 | mysql -u"$ISUCON_DB_USER" \ 22 | -p"$ISUCON_DB_PASSWORD" \ 23 | --host "$ISUCON_DB_HOST" \ 24 | --port "$ISUCON_DB_PORT" \ 25 | "$ISUCON_DB_NAME" < 1-schema.sql 26 | 27 | mysql -u"$ISUCON_DB_USER" \ 28 | -p"$ISUCON_DB_PASSWORD" \ 29 | --host "$ISUCON_DB_HOST" \ 30 | --port "$ISUCON_DB_PORT" \ 31 | "$ISUCON_DB_NAME" < 2-master-data.sql 32 | 33 | gzip -dkc 3-initial-data.sql.gz | mysql -u"$ISUCON_DB_USER" \ 34 | -p"$ISUCON_DB_PASSWORD" \ 35 | --host "$ISUCON_DB_HOST" \ 36 | --port "$ISUCON_DB_PORT" \ 37 | "$ISUCON_DB_NAME" 38 | -------------------------------------------------------------------------------- /frontend/app/utils/get-initial-data.ts: -------------------------------------------------------------------------------- 1 | type InitialChair = { 2 | id: string; 3 | owner_id: string; 4 | name: string; 5 | model: string; 6 | token: string; 7 | }; 8 | 9 | type InitialOwner = { 10 | id: string; 11 | name: string; 12 | token: string; 13 | }; 14 | 15 | type InitialUser = { 16 | id: string; 17 | username: string; 18 | firstname: string; 19 | lastname: string; 20 | token: string; 21 | date_of_birth: string; 22 | invitation_code: string; 23 | }; 24 | 25 | type initialDataType = 26 | | { 27 | owners: InitialOwner[]; 28 | chair: InitialChair; 29 | users: InitialUser[]; 30 | } 31 | | undefined; 32 | 33 | const initialData = __INITIAL_DATA__ as initialDataType; 34 | 35 | export const getOwners = (): InitialOwner[] => { 36 | return initialData?.owners ?? []; 37 | }; 38 | 39 | export const getUsers = (): InitialUser[] => { 40 | return initialData?.users ?? []; 41 | }; 42 | 43 | export const getSimulateChair = (): InitialChair | undefined => { 44 | return initialData?.chair; 45 | }; 46 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/admin/teams.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.admin; 3 | 4 | 5 | import "isuxportal/resources/team.proto"; 6 | import "isuxportal/resources/contestant.proto"; 7 | 8 | message ListTeamsQuery {} 9 | 10 | message ListTeamsResponse { 11 | repeated TeamListItem teams = 1; 12 | message TeamListItem { 13 | int64 team_id = 1; 14 | string name = 2; 15 | repeated string member_names = 3; 16 | bool final_participation = 4; 17 | bool is_student = 5; 18 | bool withdrawn = 6; 19 | bool disqualified = 7; 20 | bool hidden = 8; 21 | } 22 | } 23 | 24 | message GetTeamQuery { int64 id = 1; } 25 | 26 | message GetTeamResponse { isuxportal.proto.resources.Team team = 1; } 27 | 28 | message UpdateTeamQuery { int64 id = 1; } 29 | 30 | message UpdateTeamRequest { 31 | isuxportal.proto.resources.Team team = 1; 32 | // Update only specified contestants 33 | repeated isuxportal.proto.resources.Contestant contestants = 2; 34 | } 35 | 36 | message UpdateTeamResponse {} 37 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/menu/pulldown.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | className?: string; 3 | id: string; 4 | label?: string; 5 | items: { id: string; name: string }[]; 6 | onChange: (selected: string) => void; 7 | }; 8 | export function PulldownSelector(props: Props) { 9 | return ( 10 |
    11 | {props.label !== undefined ? ( 12 | 15 | ) : null} 16 | 32 |
    33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /bench/payment/payment.go: -------------------------------------------------------------------------------- 1 | package payment 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | ) 7 | 8 | type Status struct { 9 | Type StatusType 10 | Err error 11 | } 12 | type StatusType int 13 | 14 | const ( 15 | StatusInitial StatusType = iota 16 | StatusSuccess 17 | StatusInvalidAmount 18 | StatusInvalidToken 19 | ) 20 | 21 | func (s StatusType) String() string { 22 | switch s { 23 | case StatusInitial: 24 | return "決済処理中" 25 | case StatusSuccess: 26 | return "成功" 27 | case StatusInvalidAmount: 28 | return "決済額が不正" 29 | case StatusInvalidToken: 30 | return "決済トークンが無効" 31 | default: 32 | panic(fmt.Sprintf("unknown payment status: %d", s)) 33 | } 34 | } 35 | 36 | type Payment struct { 37 | IdempotencyKey string 38 | Token string 39 | Amount int 40 | Status Status 41 | locked atomic.Bool 42 | } 43 | 44 | func NewPayment(idk string) *Payment { 45 | p := &Payment{ 46 | IdempotencyKey: idk, 47 | Status: Status{Type: StatusInitial, Err: nil}, 48 | } 49 | p.locked.Store(false) 50 | return p 51 | } 52 | -------------------------------------------------------------------------------- /webapp/php/src/Response/ErrorResponse.php: -------------------------------------------------------------------------------- 1 | withHeader( 18 | 'Content-Type', 19 | 'application/json;charset=utf-8' 20 | ) 21 | ->withStatus($statusCode); 22 | $json = json_encode(['message' => $error->getMessage()]); 23 | if ($json === false) { 24 | $response = $response->withStatus( 25 | StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR 26 | ); 27 | $json = json_encode(['error' => 'marshaling error failed']); 28 | } 29 | $response->getBody()->write($json); 30 | error_log($error->getMessage()); 31 | return $response; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /envcheck/isucon-env-checker/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/isucon/isucon14/envcheck/isucon-env-checker 2 | 3 | go 1.23.1 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go-v2 v1.31.0 7 | github.com/aws/aws-sdk-go-v2/config v1.27.36 8 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 9 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.178.0 10 | ) 11 | 12 | require ( 13 | github.com/aws/aws-sdk-go-v2/credentials v1.17.34 // indirect 14 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect 15 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect 16 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect 17 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect 18 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect 19 | github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 // indirect 20 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 // indirect 21 | github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 // indirect 22 | github.com/aws/smithy-go v1.21.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/apt/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Use jaist mirror 3 | become: true 4 | replace: 5 | path: /etc/apt/sources.list 6 | regexp: "http://archive.ubuntu.com/ubuntu" 7 | replace: "http://ftp.jaist.ac.jp/pub/Linux/ubuntu/" 8 | backup: yes 9 | 10 | - name: Update apt cache 11 | become: true 12 | apt: 13 | update_cache: yes 14 | cache_valid_time: 0 15 | 16 | - name: Install required packages 17 | become: true 18 | apt: 19 | name: 20 | - acl 21 | - build-essential 22 | - libxml2-dev 23 | - pkg-config 24 | - libsqlite3-dev 25 | - libbz2-dev 26 | - libcurl4-openssl-dev 27 | - libpng-dev 28 | - libjpeg-dev 29 | - libonig-dev 30 | - libreadline-dev 31 | - libtidy-dev 32 | - libxslt-dev 33 | - libzip-dev 34 | - autoconf 35 | - bison 36 | - dpkg-dev 37 | - libgdbm-dev 38 | - libssl-dev 39 | - libreadline-dev 40 | - libffi-dev 41 | - zlib1g-dev 42 | - libyaml-dev 43 | - libmysqlclient-dev 44 | -------------------------------------------------------------------------------- /provisioning/ansible/README.md: -------------------------------------------------------------------------------- 1 | # Ansible 2 | 3 | ## 利用方法 4 | 5 | ``` 6 | # ベンチマーカ、webappのアーカイブをアップデート  7 | $ ./make_latest_files.sh 8 | 9 | # ローカルの場合 10 | $ ansible-playbook -i inventory/localhost application.yml 11 | $ ansible-playbook -i inventory/localhost benchmark.yml 12 | 13 | # sacloud試し環境へのリモート実行 14 | $ ansible-playbook -i inventory/sacloud application.yml 15 | $ ansible-playbook -i inventory/sacloud benchmark.yml 16 | 17 | ``` 18 | 19 | すでに対象サーバに /home/isucon/webapp/sql がある場合、tarをアップロードするだけで展開はしません 20 | 21 | ## 証明書について 22 | 23 | `dummy.crt` は下記スクリプトで生成可能 24 | 25 | ```bash 26 | #!/bin/bash 27 | openssl req -x509 -newkey ec:<(openssl ecparam -name prime256v1) -keyout server.key -out server.crt -days 365 -nodes -subj "/CN=isucon.net" 28 | ``` 29 | 30 | ## make_lastest_filesの中身 31 | 32 | ``` 33 | $ cd bench 34 | $ make linux_amd64 35 | $ mkdir -p ../provisioning/ansible/roles/bench/files 36 | $ mv bin/bench_linux_amd64 ../provisioning/ansible/roles/bench/files 37 | $ cd .. 38 | $ tar -zcvf webapp.tar.gz webapp 39 | $ mv webapp.tar.gz provisioning/ansible/roles/webapp/files 40 | $ cd provisioning/ansible 41 | ``` 42 | -------------------------------------------------------------------------------- /frontend/app/components/icon/money.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | import colors from "tailwindcss/colors"; 3 | 4 | export const MoneyIcon: FC> = (props) => { 5 | return ( 6 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | // https://github.com/google/material-design-icons/blob/master/LICENSE 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ISUCON14 Contributors 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 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/webapp/tasks/php.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Mkdir webapp for php 3 | become: true 4 | become_user: isucon 5 | ansible.builtin.file: 6 | path: /home/isucon/webapp/php 7 | state: directory 8 | 9 | - name: Put php-fpn.conf 10 | become: true 11 | ansible.builtin.copy: 12 | src: isuride.php-fpm.conf 13 | dest: /home/isucon/local/php/etc/isuride.php-fpm.conf 14 | owner: isucon 15 | group: isucon 16 | 17 | - name: Install composer 18 | become: true 19 | become_user: isucon 20 | shell: | 21 | curl -sS https://getcomposer.org/installer | /home/isucon/.x php 22 | args: 23 | chdir: /home/isucon/webapp/php 24 | 25 | - name: Build isuride-php 26 | become: true 27 | become_user: isucon 28 | shell: | 29 | /home/isucon/.x ./composer.phar install 30 | args: 31 | chdir: /home/isucon/webapp/php 32 | 33 | - name: Put systemd service 34 | become: true 35 | ansible.builtin.copy: 36 | src: isuride-php.service 37 | dest: /etc/systemd/system/ 38 | 39 | - name: Start webapp 40 | become: true 41 | service: 42 | name: isuride-php 43 | enabled: false 44 | state: stopped 45 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/resources/benchmark_job.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.resources; 3 | 4 | 5 | import "isuxportal/resources/benchmark_result.proto"; 6 | import "isuxportal/resources/contestant_instance.proto"; 7 | import "isuxportal/resources/team.proto"; 8 | 9 | import "google/protobuf/timestamp.proto"; 10 | 11 | message BenchmarkJob { 12 | int64 id = 1; 13 | int64 team_id = 2; 14 | int64 target_id = 3; 15 | Status status = 4; 16 | enum Status { 17 | PENDING = 0; 18 | RUNNING = 1; 19 | ERRORED = 2; 20 | CANCELLED = 3; 21 | FINISHED = 4; 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 is only available at ... 34 | Team team = 16; 35 | 36 | // target & result & execution are only available at GetBenchmarkJobResponse 37 | ContestantInstance target = 17; 38 | BenchmarkResult result = 18; 39 | } 40 | -------------------------------------------------------------------------------- /frontend/app/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | OwnerGetChairsResponse, 3 | OwnerGetSalesResponse, 4 | } from "./api/api-components"; 5 | 6 | export type AccessToken = string; 7 | 8 | export type ClientAppChair = { 9 | id: string; 10 | name: string; 11 | model: string; 12 | stats: Partial<{ 13 | total_rides_count: number; 14 | total_evaluation_avg: number; 15 | }>; 16 | }; 17 | 18 | export type Coordinate = { latitude: number; longitude: number }; 19 | 20 | export type DisplayPos = { x: number; y: number }; 21 | 22 | export type Distance = { horizontalDistance: number; verticalDistance: number }; 23 | 24 | export type NearByChair = { 25 | id: string; 26 | name: string; 27 | model: string; 28 | current_coordinate: Coordinate; 29 | }; 30 | 31 | export type CampaignData = { 32 | invitationCode: string; 33 | registedAt: string; 34 | used: boolean; 35 | }; 36 | 37 | export type OwnerChairs = OwnerGetChairsResponse["chairs"]; 38 | export type OwnerSales = OwnerGetSalesResponse; 39 | 40 | export type SimulatorChair = { 41 | id: string; 42 | name: string; 43 | model: string; 44 | token: string; 45 | coordinate: Coordinate; 46 | }; 47 | -------------------------------------------------------------------------------- /bench/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TASK_METADATA_URI=$ECS_CONTAINER_METADATA_URI_V4/task 4 | 5 | ZONE_NAME=$(curl -s $TASK_METADATA_URI | jq -r .AvailabilityZone) 6 | ZONE_ID=$(aws ec2 describe-availability-zones | 7 | jq -r ".AvailabilityZones[] | select(.ZoneName==\"${ZONE_NAME}\").ZoneId") 8 | export ISUXPORTAL_SUPERVISOR_INSTANCE_NAME="${ZONE_ID}" 9 | 10 | 11 | # クラスタARNを取得し、クラスタIDを抽出 12 | CLUSTER_ARN=$(curl -s $TASK_METADATA_URI | jq -r '.Cluster') 13 | CLUSTER_ID=$(echo $CLUSTER_ARN | awk -F'/' '{print $2}') 14 | 15 | # タスクARNを取得 16 | TASK_ARN=$(curl -s $TASK_METADATA_URI | jq -r '.TaskARN') 17 | TASK_ID=$(echo $TASK_ARN | awk -F'/' '{print $3}') 18 | 19 | # ENI (Elastic Network Interface) IDを取得 20 | ENI_ID=$(aws ecs describe-tasks \ 21 | --cluster $CLUSTER_ID \ 22 | --tasks $TASK_ID \ 23 | --query "tasks[0].attachments[0].details[?name=='networkInterfaceId'].value" \ 24 | --output text) 25 | 26 | # ENIに関連付けられたパブリックIPを取得 27 | PUBLIC_IP=$(aws ec2 describe-network-interfaces \ 28 | --network-interface-ids $ENI_ID \ 29 | --query "NetworkInterfaces[0].Association.PublicIp" \ 30 | --output text) 31 | 32 | exec "$@" --payment-url http://$PUBLIC_IP:12345 33 | -------------------------------------------------------------------------------- /frontend/app/components/icon/account.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | import colors from "tailwindcss/colors"; 3 | 4 | export const AccountIcon: FC> = (props) => { 5 | return ( 6 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | // https://github.com/google/material-design-icons/blob/master/LICENSE 20 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/envcheck/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: mkdir for envcheck 3 | become: true 4 | ansible.builtin.file: 5 | path: /opt/isucon-env-checker 6 | state: directory 7 | 8 | - name: Send envcheck binary 9 | become: true 10 | ansible.builtin.copy: 11 | src: envcheck 12 | dest: /opt/isucon-env-checker/envcheck 13 | mode: 0755 14 | owner: root 15 | group: root 16 | 17 | - name: Put run script 18 | become: true 19 | ansible.builtin.copy: 20 | src: run-isucon-env-checker.sh 21 | dest: /opt/isucon-env-checker/run-isucon-env-checker.sh 22 | mode: 0755 23 | owner: root 24 | group: root 25 | 26 | - name: Put warmup script 27 | become: true 28 | ansible.builtin.copy: 29 | src: warmup.sh 30 | dest: /opt/isucon-env-checker/warmup.sh 31 | mode: 0755 32 | owner: root 33 | group: root 34 | 35 | - name: Put systemd service 36 | become: true 37 | ansible.builtin.copy: 38 | src: envcheck.service 39 | dest: /etc/systemd/system/ 40 | 41 | - name: Start envcheck 42 | become: true 43 | ansible.builtin.systemd: 44 | name: envcheck 45 | enabled: yes 46 | daemon_reload: true 47 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/admin/clarifications.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.admin; 3 | 4 | 5 | import "isuxportal/resources/clarification.proto"; 6 | 7 | message ListClarificationsQuery { 8 | // optional to filter 9 | int64 team_id = 1; 10 | bool unanswered_only = 2; 11 | } 12 | 13 | message ListClarificationsResponse { 14 | repeated isuxportal.proto.resources.Clarification clarifications = 1; 15 | } 16 | 17 | message GetClarificationQuery { int64 id = 1; } 18 | 19 | message GetClarificationResponse { 20 | isuxportal.proto.resources.Clarification clarification = 1; 21 | } 22 | 23 | message RespondClarificationRequest { 24 | int64 id = 1; 25 | bool disclose = 2; 26 | string answer = 3; 27 | // optional to override original question 28 | string question = 4; 29 | } 30 | 31 | message RespondClarificationResponse { 32 | isuxportal.proto.resources.Clarification clarification = 1; 33 | } 34 | 35 | message CreateClarificationRequest { 36 | string answer = 2; 37 | string question = 3; 38 | int64 team_id = 4; 39 | } 40 | 41 | message CreateClarificationResponse { 42 | isuxportal.proto.resources.Clarification clarification = 1; 43 | } 44 | -------------------------------------------------------------------------------- /webapp/php/src/Foundation/Handlers/HttpErrorHandler.php: -------------------------------------------------------------------------------- 1 | exception; 16 | $statusCode = 500; 17 | $message = 'server error'; 18 | 19 | if ($exception instanceof HttpException) { 20 | $statusCode = $exception->getCode(); 21 | $message = $exception->getMessage(); 22 | } elseif ($this->displayErrorDetails) { 23 | $message = $exception->getMessage(); 24 | } 25 | 26 | $encodedPayload = json_encode(['error' => $message], JSON_PRETTY_PRINT); 27 | 28 | $response = $this->responseFactory->createResponse($statusCode); 29 | $response->getBody()->write($encodedPayload); 30 | 31 | return $response->withHeader( 32 | 'Content-Type', 33 | 'application/json;charset=utf-8' 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /webapp/php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isucon/isucon14-php", 3 | "require": { 4 | "php": "^8.3", 5 | "ext-json": "*", 6 | "ext-curl": "*", 7 | "ext-pdo": "*", 8 | "ext-ctype": "*", 9 | "monolog/monolog": "^3.7", 10 | "slim/psr7": "^1.5", 11 | "slim/slim": "^4.10", 12 | "symfony/uid": "^7.1", 13 | "ybelenko/openapi-data-mocker": "^1.0", 14 | "ybelenko/openapi-data-mocker-server-middleware": "^1.2" 15 | }, 16 | "require-dev": { 17 | "jangregor/phpstan-prophecy": "^1.0.0", 18 | "phpspec/prophecy-phpunit": "^2.0", 19 | "phpstan/extension-installer": "^1.2.0", 20 | "phpstan/phpstan": "^1.8", 21 | "phpunit/phpunit": "^9.5.26", 22 | "squizlabs/php_codesniffer": "^3.7" 23 | }, 24 | "config": { 25 | "allow-plugins": { 26 | "phpstan/extension-installer": true 27 | }, 28 | "process-timeout": 0, 29 | "sort-packages": true 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "IsuRide\\": "src/" 34 | }, 35 | "files": [ 36 | "src/function.php" 37 | ] 38 | }, 39 | "scripts": { 40 | "generate": [ 41 | "openapi-generator generate -i ../openapi.yaml -g php -o ./ --config config.yaml" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/admin/benchmark.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.admin; 3 | 4 | 5 | import "isuxportal/resources/benchmark_job.proto"; 6 | 7 | message ListBenchmarkJobsQuery { 8 | // optional filter by team_id 9 | int64 team_id = 1; 10 | // optional filter by status 11 | isuxportal.proto.resources.BenchmarkJob.Status status = 2; 12 | // optional paganation 13 | int64 page = 3; 14 | // return only failed 15 | bool failed_only = 4; 16 | } 17 | 18 | message ListBenchmarkJobsResponse { 19 | repeated isuxportal.proto.resources.BenchmarkJob jobs = 1; 20 | int64 max_page = 2; 21 | } 22 | 23 | message EnqueueBenchmarkJobRequest { 24 | int64 team_id = 1; 25 | // target ContestantInstance id 26 | int64 target_id = 2; 27 | } 28 | 29 | message EnqueueBenchmarkJobResponse { 30 | isuxportal.proto.resources.BenchmarkJob job = 1; 31 | } 32 | 33 | message CancelBenchmarkJobQuery { int64 id = 1; } 34 | 35 | message CancelBenchmarkJobResponse { 36 | isuxportal.proto.resources.BenchmarkJob job = 1; 37 | } 38 | 39 | // Query parameter 40 | message GetBenchmarkJobQuery { int64 id = 1; } 41 | 42 | message GetBenchmarkJobResponse { 43 | isuxportal.proto.resources.BenchmarkJob job = 1; 44 | } 45 | -------------------------------------------------------------------------------- /webapp/ruby/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | base64 (0.2.0) 5 | bigdecimal (3.1.8) 6 | logger (1.6.1) 7 | multi_json (1.15.0) 8 | mustermann (3.0.3) 9 | ruby2_keywords (~> 0.0.1) 10 | mysql2 (0.5.6) 11 | mysql2-cs-bind (0.1.1) 12 | mysql2 13 | nio4r (2.7.4) 14 | puma (6.5.0) 15 | nio4r (~> 2.0) 16 | rack (3.1.8) 17 | rack-protection (4.1.1) 18 | base64 (>= 0.1.0) 19 | logger (>= 1.6.0) 20 | rack (>= 3.0.0, < 4) 21 | rack-session (2.0.0) 22 | rack (>= 3.0.0) 23 | ruby2_keywords (0.0.5) 24 | sinatra (4.1.1) 25 | logger (>= 1.6.0) 26 | mustermann (~> 3.0) 27 | rack (>= 3.0.0, < 4) 28 | rack-protection (= 4.1.1) 29 | rack-session (>= 2.0.0, < 3) 30 | tilt (~> 2.0) 31 | sinatra-contrib (4.1.1) 32 | multi_json (>= 0.0.2) 33 | mustermann (~> 3.0) 34 | rack-protection (= 4.1.1) 35 | sinatra (= 4.1.1) 36 | tilt (~> 2.0) 37 | tilt (2.4.0) 38 | ulid (1.4.0) 39 | 40 | PLATFORMS 41 | ruby 42 | 43 | DEPENDENCIES 44 | bigdecimal 45 | mysql2 46 | mysql2-cs-bind 47 | puma 48 | sinatra 49 | sinatra-contrib 50 | ulid 51 | 52 | BUNDLED WITH 53 | 2.5.22 54 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/isuadmin-user/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create isuadmin group 3 | become: true 4 | group: 5 | name: isuadmin 6 | gid: 1110 7 | state: present 8 | system: no 9 | 10 | - name: Create isuadmin user 11 | become: true 12 | user: 13 | name: isuadmin 14 | uid: 1110 15 | group: isuadmin 16 | password: isuadmin 17 | home: /home/isuadmin 18 | shell: /bin/bash 19 | state: present 20 | system: no 21 | 22 | - name: Chmod isuadmin home directory 23 | become: true 24 | file: 25 | path: /home/isuadmin 26 | mode: 0755 27 | 28 | - name: Create .ssh directory for isuadmin 29 | become: true 30 | file: 31 | path: /home/isuadmin/.ssh 32 | state: directory 33 | mode: 0700 34 | owner: isuadmin 35 | group: isuadmin 36 | 37 | - name: Put authorized_keys 38 | become: true 39 | copy: 40 | dest: /home/isuadmin/.ssh/authorized_keys 41 | src: authorized_keys 42 | mode: 0600 43 | owner: isuadmin 44 | group: isuadmin 45 | 46 | - name: Add isuadmin to sudoers 47 | become: true 48 | copy: 49 | content: "isuadmin ALL=(ALL) NOPASSWD:ALL\n" 50 | dest: /etc/sudoers.d/99-isuadmin-user 51 | owner: root 52 | group: root 53 | mode: 0440 54 | -------------------------------------------------------------------------------- /provisioning/README.md: -------------------------------------------------------------------------------- 1 | # AMIをビルドするスクリプト 2 | 3 | # 手動でのビルド手順 4 | 5 | AMIイメージは 6 | 7 | - 各言語の実行環境を含むベースイメージ 8 | - アプリケーションコードを含む競技イメージ 9 | 10 | の2つに別れています。 11 | 12 | 競技イメージはベースイメージをソースにビルドを実行するため、作成するAWS環境にベースイメージが存在しない場合、必ず一度ベースイメージの作成を実行する必要があります。 13 | 実行環境に修正を加えた場合も手動で再度ベースイメージのビルドが必要になります。 14 | (所要時間20分程度かかる可能性があります) 15 | 16 | ベースイメージを作成後、アプリケーションコードの変更のみ反映したい場合は競技イメージのビルドのみを行うことが出来ます。 17 | 18 | ``` 19 | # AMIを作成するAWS環境にログイン 20 | aws --profile ${aws_env} sso login 21 | 22 | # ビルド実行(ベースイメージ) 23 | cd provisioning/packer 24 | make base-build 25 | 26 | # ビルド実行(アプリケーション) 27 | cd provisioning/packer 28 | make 29 | ``` 30 | 31 | ## 前提条件 32 | 33 | 以下のものがインストールされていること 34 | - packer 35 | - go-task (バックエンドのビルドのため) 36 | - pnpm (フロントのビルドのため) 37 | 38 | # 留意事項 39 | 40 | - CIは動作未確認のためまだ動かないようにしてあります 41 | - 作問合宿用の暫定として `trial.isucon14.net` の自己証明書を含んでいます 42 | 43 | ## フロントエンド 44 | `frontend/Makefile` に依存 45 | `pnpm run build` した結果の `build/client` 以下のファイルをnginxのディレクトリにデプロイしています 46 | 47 | ## API 48 | 現時点で `/api`, `/app`, `/provider`, `/chair` 宛のリクエストが 8080にproxyされるようにnginxが構成されています 49 | エンドポイントの増減がある場合はnginxの設定ファイルの修正が必要です 50 | 51 | APIサーバの動作に必要な環境変数は `provisioning/ansible/roles/isucon-user/templates/env.sh` で定義しています 52 | 53 | ## DB 54 | 初期化処理として `webapp/sql/init.sh` を流しています 55 | 初期化したい処理が追加になった場合は修正が必要です 56 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/xbuild/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install git 3 | become: true 4 | apt: 5 | name: git 6 | state: present 7 | 8 | - name: Install xbuild 9 | become: true 10 | git: 11 | repo: https://github.com/tagomoris/xbuild.git 12 | dest: /opt/xbuild 13 | version: master 14 | depth: 1 15 | 16 | - name: Create /home/isucon/local directory 17 | become: true 18 | file: 19 | path: /home/isucon/local 20 | state: directory 21 | mode: 0755 22 | owner: isucon 23 | group: isucon 24 | 25 | - name: Install golang 26 | become: true 27 | become_user: isucon 28 | shell: 29 | cmd: /opt/xbuild/go-install 1.23.2 /home/isucon/local/golang 30 | 31 | - name: Copy /home/isucon/.local.env 32 | become: true 33 | become_user: isucon 34 | copy: 35 | src: .local.env 36 | dest: /home/isucon/.local.env 37 | mode: 0644 38 | 39 | - name: Copy /home/isucon/.x 40 | become: true 41 | become_user: isucon 42 | copy: 43 | src: .x 44 | dest: /home/isucon/.x 45 | mode: 0755 46 | 47 | - name: modify /home/isucon/.profile 48 | become: true 49 | become_user: isucon 50 | lineinfile: 51 | path: /home/isucon/.profile 52 | regexp: '^source \$HOME/\.local\.env$' 53 | line: "source $HOME/.local.env" 54 | insertafter: EOF 55 | -------------------------------------------------------------------------------- /webapp/perl/lib/Mojo/Isuride/Time.pm: -------------------------------------------------------------------------------- 1 | package Mojo::Isuride::Time; 2 | use v5.40; 3 | use utf8; 4 | 5 | # Perlでミリ秒単位のUNIX時間を取得するユーティリティ 6 | use Exporter 'import'; 7 | 8 | our @EXPORT_OK = qw( 9 | unix_milli_from_str 10 | ); 11 | 12 | use Time::Moment; 13 | 14 | # DateTime::Format::Strptimeの場合は以下のフォーマット 15 | # use constant FORMAT_DATETIME => DateTime::Format::Strptime->new( 16 | # pattern => '%Y-%m-%d %H:%M:%S.%3N', 17 | # time_zone => 'UTC', 18 | # ); 19 | 20 | use constant FORMAT_TIME => qr/\A(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{1,9})/; 21 | 22 | sub unix_milli_from_str ($str) { 23 | if ($str =~ FORMAT_TIME) { 24 | my ($year, $month, $day, $hour, $minute, $second, $fraction) = ($1, $2, $3, $4, $5, $6, $7); 25 | my $nanosecond = $fraction * (10**(9 - length($fraction))); 26 | my $tm = Time::Moment->new( 27 | year => $year, 28 | month => $month, 29 | day => $day, 30 | hour => $hour, 31 | minute => $minute, 32 | second => $second, 33 | nanosecond => $nanosecond, 34 | ); 35 | my $milliepoch = $tm->epoch * 1000 + $tm->millisecond; 36 | return $milliepoch; 37 | } 38 | die "Invalid time format: $str"; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /webapp/ruby/lib/isuride/internal_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'isuride/base_handler' 4 | 5 | module Isuride 6 | class InternalHandler < BaseHandler 7 | # このAPIをインスタンス内から一定間隔で叩かせることで、椅子とライドをマッチングさせる 8 | # GET /api/internal/matching 9 | get '/matching' do 10 | # MEMO: 一旦最も待たせているリクエストに適当な空いている椅子マッチさせる実装とする。おそらくもっといい方法があるはず… 11 | ride = db.query('SELECT * FROM rides WHERE chair_id IS NULL ORDER BY created_at LIMIT 1').first 12 | unless ride 13 | halt 204 14 | end 15 | 16 | 10.times do 17 | matched = db.query('SELECT * FROM chairs INNER JOIN (SELECT id FROM chairs WHERE is_active = TRUE ORDER BY RAND() LIMIT 1) AS tmp ON chairs.id = tmp.id LIMIT 1').first 18 | unless matched 19 | halt 204 20 | end 21 | 22 | empty = db.xquery('SELECT COUNT(*) = 0 FROM (SELECT COUNT(chair_sent_at) = 6 AS completed FROM ride_statuses WHERE ride_id IN (SELECT id FROM rides WHERE chair_id = ?) GROUP BY ride_id) is_completed WHERE completed = FALSE', matched.fetch(:id), as: :array).first[0] 23 | if empty > 0 24 | db.xquery('UPDATE rides SET chair_id = ? WHERE id = ?', matched.fetch(:id), ride.fetch(:id)) 25 | break 26 | end 27 | end 28 | 29 | 204 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /webapp/perl/lib/Kossy/Isuride/Time.pm: -------------------------------------------------------------------------------- 1 | package Kossy::Isuride::Time; 2 | use v5.40; 3 | use utf8; 4 | 5 | # Perlでミリ秒単位のUNIX時間を取得するユーティリティ 6 | use Exporter 'import'; 7 | 8 | our @EXPORT_OK = qw( 9 | unix_milli_from_str 10 | ); 11 | 12 | use Time::Moment; 13 | 14 | # DateTime::Format::Strptimeの場合は以下のフォーマット 15 | # use constant FORMAT_DATETIME => DateTime::Format::Strptime->new( 16 | # pattern => '%Y-%m-%d %H:%M:%S.%3N', 17 | # time_zone => 'UTC', 18 | # ); 19 | 20 | use constant FORMAT_TIME => qr/\A(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{1,9})/; 21 | 22 | sub unix_milli_from_str ($str) { 23 | if ($str =~ FORMAT_TIME) { 24 | my ($year, $month, $day, $hour, $minute, $second, $fraction) = ($1, $2, $3, $4, $5, $6, $7); 25 | my $nanosecond = $fraction * (10**(9 - length($fraction))); 26 | my $tm = Time::Moment->new( 27 | year => $year, 28 | month => $month, 29 | day => $day, 30 | hour => $hour, 31 | minute => $minute, 32 | second => $second, 33 | nanosecond => $nanosecond, 34 | ); 35 | my $milliepoch = $tm->epoch * 1000 + $tm->millisecond; 36 | return $milliepoch; 37 | } 38 | die "Invalid time format: $str"; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /bench/benchmarker/webapp/api/oas_cfg_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by ogen, DO NOT EDIT. 2 | 3 | package api 4 | 5 | import ( 6 | "net/http" 7 | 8 | ht "github.com/ogen-go/ogen/http" 9 | ) 10 | 11 | type ( 12 | optionFunc[C any] func(*C) 13 | ) 14 | 15 | type clientConfig struct { 16 | Client ht.Client 17 | } 18 | 19 | // ClientOption is client config option. 20 | type ClientOption interface { 21 | applyClient(*clientConfig) 22 | } 23 | 24 | var _ ClientOption = (optionFunc[clientConfig])(nil) 25 | 26 | func (o optionFunc[C]) applyClient(c *C) { 27 | o(c) 28 | } 29 | 30 | func newClientConfig(opts ...ClientOption) clientConfig { 31 | cfg := clientConfig{ 32 | Client: http.DefaultClient, 33 | } 34 | for _, opt := range opts { 35 | opt.applyClient(&cfg) 36 | } 37 | return cfg 38 | } 39 | 40 | type baseClient struct { 41 | cfg clientConfig 42 | } 43 | 44 | func (cfg clientConfig) baseClient() (c baseClient, err error) { 45 | c = baseClient{cfg: cfg} 46 | return c, nil 47 | } 48 | 49 | // Option is config option. 50 | type Option interface { 51 | ClientOption 52 | } 53 | 54 | // WithClient specifies http client to use. 55 | func WithClient(client ht.Client) ClientOption { 56 | return optionFunc[clientConfig](func(cfg *clientConfig) { 57 | if client != nil { 58 | cfg.Client = client 59 | } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /frontend/app/components/modules/simulator-parts/simulator-chair-status-label.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps, FC } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | import colors from "tailwindcss/colors"; 4 | import { RideStatus } from "~/api/api-schemas"; 5 | import { Text } from "~/components/primitives/text/text"; 6 | 7 | const StatusList = { 8 | MATCHING: ["空車", colors.sky["500"]], 9 | ENROUTE: ["迎車", colors.amber["500"]], 10 | PICKUP: ["乗車待ち", colors.amber["500"]], 11 | CARRYING: ["賃走", colors.red["500"]], 12 | ARRIVED: ["到着", colors.emerald["500"]], 13 | COMPLETED: ["空車", colors.sky["500"]], 14 | } as const satisfies Record; 15 | 16 | export const SimulatorChairRideStatus: FC< 17 | ComponentProps<"div"> & { 18 | currentStatus: RideStatus; 19 | } 20 | > = ({ currentStatus, className, ...props }) => { 21 | const [labelName, color] = StatusList[currentStatus]; 22 | return ( 23 |
    27 |
    32 | 33 | {labelName} 34 | 35 |
    36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /webapp/go/go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 3 | github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= 4 | github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 5 | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= 6 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 7 | github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= 8 | github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= 9 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 10 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 11 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 12 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 13 | github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= 14 | github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= 15 | github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= 16 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/base/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: "roles/common/tasks/timezone: Set timezone to UTC" 2 | become: true 3 | timezone: 4 | name: UTC 5 | 6 | - name: Copy sshd_config.d/pubkey.conf 7 | become: true 8 | copy: 9 | src: pubkey.conf 10 | dest: /etc/ssh/sshd_config.d/pubkey.conf 11 | mode: 0644 12 | 13 | - name: Set sysctl local_port_range 14 | become: true 15 | sysctl: 16 | name: net.ipv4.ip_local_port_range 17 | value: "10000 65535" 18 | state: present 19 | sysctl_set: true 20 | reload: true 21 | 22 | - name: Configure pam limits 23 | become: true 24 | pam_limits: 25 | domain: "*" 26 | limit_type: "{{item.limit_type}}" 27 | limit_item: "{{item.limit_item}}" 28 | value: "{{item.value}}" 29 | with_items: 30 | - { limit_type: "-", limit_item: "nofile", value: 655360 } 31 | - { limit_type: "-", limit_item: "nproc", value: 655360 } 32 | - { limit_type: "soft", limit_item: "memlock", value: unlimited } 33 | - { limit_type: "hard", limit_item: "memlock", value: unlimited } 34 | 35 | - name: add local isuride.xiv.isucon.net IP to /etc/hosts 36 | become: true 37 | ansible.builtin.blockinfile: 38 | path: /etc/hosts 39 | state: present 40 | block: | 41 | 42 | # ISURIDE IP for matching requests 43 | 127.0.0.1 isuride.xiv.isucon.net 44 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/isucon-user/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create isucon group 3 | become: true 4 | group: 5 | name: isucon 6 | gid: 1100 7 | state: present 8 | system: no 9 | 10 | - name: Create isucon user 11 | become: true 12 | user: 13 | name: isucon 14 | uid: 1100 15 | group: isucon 16 | password: isucon 17 | home: /home/isucon 18 | shell: /bin/bash 19 | state: present 20 | system: no 21 | 22 | - name: Chmod isucon home directory 23 | become: true 24 | file: 25 | path: /home/isucon 26 | mode: 0755 27 | 28 | - name: Create .ssh directory for isucon 29 | become: true 30 | file: 31 | path: /home/isucon/.ssh 32 | state: directory 33 | mode: 0700 34 | owner: isucon 35 | group: isucon 36 | 37 | - name: Remove authorized_keys 38 | become: true 39 | file: 40 | state: absent 41 | path: /home/isucon/.ssh/authorized_keys 42 | 43 | - name: Add isucon to sudoers 44 | become: true 45 | copy: 46 | content: "isucon ALL=(ALL) NOPASSWD:ALL\n" 47 | dest: /etc/sudoers.d/99-isucon-user 48 | owner: root 49 | group: root 50 | mode: 0440 51 | 52 | - name: Put systemd service env file 53 | become: true 54 | ansible.builtin.template: 55 | src: env.sh 56 | dest: /home/isucon/ 57 | owner: isucon 58 | group: isucon 59 | mode: 0755 -------------------------------------------------------------------------------- /webapp/php/src/Foundation/ResponseEmitter/ResponseEmitter.php: -------------------------------------------------------------------------------- 1 | withHeader('Access-Control-Allow-Credentials', 'true') 19 | ->withHeader('Access-Control-Allow-Origin', $origin) 20 | ->withHeader( 21 | 'Access-Control-Allow-Headers', 22 | 'X-Requested-With, Content-Type, Accept, Origin, Authorization', 23 | ) 24 | ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS') 25 | ->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') 26 | ->withAddedHeader('Cache-Control', 'post-check=0, pre-check=0') 27 | ->withHeader('Pragma', 'no-cache'); 28 | 29 | if (ob_get_contents()) { 30 | ob_clean(); 31 | } 32 | parent::emit($response); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /webapp/php/app/config.php: -------------------------------------------------------------------------------- 1 | function (): PDO { 12 | $host = getenv('ISUCON_DB_HOST') ?: '127.0.0.1'; 13 | $port = getenv('ISUCON_DB_PORT') ?: '3306'; 14 | $username = getenv('ISUCON_DB_USER') ?: 'isucon'; 15 | $password = getenv('ISUCON_DB_PASSWORD') ?: 'isucon'; 16 | $database = getenv('ISUCON_DB_NAME') ?: 'isuride'; 17 | $dsn = vsprintf('mysql:host=%s;dbname=%s;port=%d;charset=utf8mb4', [ 18 | $host, 19 | $database, 20 | $port 21 | ]); 22 | return new PDO($dsn, $username, $password, [ 23 | PDO::ATTR_PERSISTENT => true, 24 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 25 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 26 | ]); 27 | }, 28 | 'logger' => function (): Logger { 29 | $logger = new Logger('isuride'); 30 | $logger->useLoggingLoopDetection(false); 31 | $logger->pushHandler( 32 | new StreamHandler('php://stdout', Level::Info) 33 | ); 34 | return $logger; 35 | }, 36 | 'payment_gateway' => function (): PostPayment { 37 | return new PostPayment(); 38 | } 39 | ]; 40 | -------------------------------------------------------------------------------- /bench/benchmarker/webapp/client_initialize.go: -------------------------------------------------------------------------------- 1 | package webapp 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "net/http" 9 | 10 | "github.com/isucon/isucon14/bench/benchmarker/webapp/api" 11 | ) 12 | 13 | type PostInitializeResponse struct { 14 | Language string `json:"language"` 15 | } 16 | 17 | func (c *Client) PostInitialize(ctx context.Context, reqBody *api.PostInitializeReq) (*PostInitializeResponse, error) { 18 | reqBodyBuf, err := reqBody.MarshalJSON() 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | req, err := c.agent.NewRequest(http.MethodPost, "/api/initialize", bytes.NewReader(reqBodyBuf)) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | for _, modifier := range c.requestModifiers { 29 | modifier(req) 30 | } 31 | 32 | resp, err := c.agent.Do(ctx, req) 33 | if err != nil { 34 | return nil, fmt.Errorf("POST /api/initialize のリクエストが失敗しました: %w", err) 35 | } 36 | defer closeBody(resp) 37 | 38 | if resp.StatusCode != http.StatusOK { 39 | return nil, fmt.Errorf("POST /api/initialize へのリクエストに対して、期待されたHTTPステータスコードが確認できませんでした (expected:%d, actual:%d)", http.StatusOK, resp.StatusCode) 40 | } 41 | 42 | var response PostInitializeResponse 43 | if json.NewDecoder(resp.Body).Decode(&response) != nil { 44 | return nil, fmt.Errorf("POST /api/initializeのJSONのdecodeに失敗しました: %w", err) 45 | } 46 | 47 | return &response, nil 48 | } 49 | -------------------------------------------------------------------------------- /development/compose-ruby.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | image: public.ecr.aws/docker/library/nginx:latest 4 | container_name: nginx 5 | volumes: 6 | - ./nginx/conf.d:/etc/nginx/conf.d:ro 7 | - ../frontend/build/client:/home/isucon/webapp/public:ro 8 | ports: 9 | - "8080:80" 10 | depends_on: 11 | - webapp 12 | webapp: 13 | build: 14 | context: ../webapp/ruby 15 | dockerfile: ../../development/dockerfiles/Dockerfile.ruby 16 | expose: 17 | - 8080 18 | volumes: 19 | - ../webapp/sql:/home/isucon/webapp/sql 20 | working_dir: /home/isucon/webapp/ruby 21 | depends_on: 22 | db: 23 | condition: service_healthy 24 | environment: 25 | ISUCON_DB_HOST: db 26 | extra_hosts: 27 | - "host.docker.internal:host-gateway" 28 | db: 29 | image: public.ecr.aws/docker/library/mysql:8 30 | environment: 31 | ENV: local-dev 32 | MYSQL_ROOT_PASSWORD: isucon 33 | MYSQL_DATABASE: isucon 34 | ports: 35 | - 3306:3306 36 | volumes: 37 | - ./mysql/db:/var/lib/mysql 38 | - ../webapp/sql:/docker-entrypoint-initdb.d 39 | healthcheck: 40 | test: mysqladmin ping -h 127.0.0.1 -uisucon -pisucon 41 | start_period: 60s 42 | matcher: 43 | image: mirror.gcr.io/curlimages/curl:latest 44 | command: /bin/sh -c "while true; do curl -s http://nginx/api/internal/matching; sleep 0.5; done" 45 | -------------------------------------------------------------------------------- /frontend/app/utils/post-message.ts: -------------------------------------------------------------------------------- 1 | export const MessageTypes = { 2 | ClientReady: "isuride.client.ready", // クライアントの画面準備完了 3 | ClientRideRequested: "isuride.client.running", // クライアントでISURIDEが実行中 4 | SimulatorConfing: "isuride.simulator.config", // シミュレーターからの設定値変更 5 | } as const; 6 | 7 | export type Message = { 8 | ClientReady: { 9 | type: typeof MessageTypes.ClientReady; 10 | payload: { ready?: boolean }; 11 | }; 12 | ClientRideRequested: { 13 | type: typeof MessageTypes.ClientRideRequested; 14 | payload: { rideId?: string }; 15 | }; 16 | SimulatorConfing: { 17 | type: typeof MessageTypes.SimulatorConfing; 18 | payload: { 19 | ghostChairEnabled?: boolean; 20 | }; 21 | }; 22 | }; 23 | 24 | export const sendClientReady = ( 25 | target: Window, 26 | payload: NonNullable, 27 | ) => { 28 | target.postMessage({ type: MessageTypes.ClientReady, payload }, "*"); 29 | }; 30 | 31 | export const sendClientRideRequested = ( 32 | target: Window, 33 | payload: NonNullable, 34 | ) => { 35 | target.postMessage({ type: MessageTypes.ClientRideRequested, payload }, "*"); 36 | }; 37 | 38 | export const sendSimulatorConfig = ( 39 | target: Window, 40 | payload: NonNullable, 41 | ) => { 42 | target.postMessage({ type: MessageTypes.SimulatorConfing, payload }, "*"); 43 | }; 44 | -------------------------------------------------------------------------------- /development/compose-go.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | image: public.ecr.aws/docker/library/nginx:latest 4 | container_name: nginx 5 | volumes: 6 | - ./nginx/conf.d:/etc/nginx/conf.d:ro 7 | - ../frontend/build/client:/home/isucon/webapp/public:ro 8 | ports: 9 | - "8080:80" 10 | depends_on: 11 | - webapp 12 | webapp: 13 | build: 14 | context: ../webapp/go 15 | dockerfile: ../../development/dockerfiles/Dockerfile.go 16 | image: webapp-go 17 | expose: 18 | - 8080 19 | volumes: 20 | - ../webapp/sql:/home/isucon/webapp/sql 21 | working_dir: /home/isucon/webapp/go 22 | depends_on: 23 | db: 24 | condition: service_healthy 25 | environment: 26 | ISUCON_DB_HOST: db 27 | extra_hosts: 28 | - "host.docker.internal:host-gateway" 29 | db: 30 | image: public.ecr.aws/docker/library/mysql:8 31 | environment: 32 | ENV: local-dev 33 | MYSQL_ROOT_PASSWORD: isucon 34 | MYSQL_DATABASE: isucon 35 | ports: 36 | - 3306:3306 37 | volumes: 38 | - ./mysql/db:/var/lib/mysql 39 | - ../webapp/sql:/docker-entrypoint-initdb.d 40 | healthcheck: 41 | test: mysqladmin ping -h 127.0.0.1 -uisucon -pisucon 42 | start_period: 60s 43 | matcher: 44 | image: mirror.gcr.io/curlimages/curl:latest 45 | command: /bin/sh -c "while true; do curl -s http://nginx/api/internal/matching; sleep 0.5; done" 46 | -------------------------------------------------------------------------------- /development/compose-rust.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | image: public.ecr.aws/docker/library/nginx:latest 4 | container_name: nginx 5 | volumes: 6 | - ./nginx/conf.d:/etc/nginx/conf.d:ro 7 | - ../frontend/build/client:/home/isucon/webapp/public:ro 8 | ports: 9 | - "8080:80" 10 | depends_on: 11 | - webapp 12 | webapp: 13 | build: 14 | context: ../webapp/rust 15 | dockerfile: ../../development/dockerfiles/Dockerfile.rust 16 | image: webapp-rust 17 | expose: 18 | - 8080 19 | volumes: 20 | - ../webapp/sql:/home/isucon/webapp/sql 21 | working_dir: /home/isucon/webapp/rust 22 | depends_on: 23 | db: 24 | condition: service_healthy 25 | environment: 26 | ISUCON_DB_HOST: db 27 | extra_hosts: 28 | - "host.docker.internal:host-gateway" 29 | db: 30 | image: public.ecr.aws/docker/library/mysql:8 31 | environment: 32 | ENV: local-dev 33 | MYSQL_ROOT_PASSWORD: isucon 34 | MYSQL_DATABASE: isuride 35 | ports: 36 | - 3306:3306 37 | volumes: 38 | - ./mysql/db:/var/lib/mysql 39 | - ../webapp/sql:/docker-entrypoint-initdb.d 40 | healthcheck: 41 | test: mysqladmin ping -h 127.0.0.1 -uisucon -pisucon 42 | start_period: 60s 43 | matcher: 44 | image: mirror.gcr.io/curlimages/curl:latest 45 | command: /bin/sh -c "while true; do curl -s http://nginx/api/internal/matching; sleep 0.5; done" 46 | -------------------------------------------------------------------------------- /development/compose-python.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | image: public.ecr.aws/docker/library/nginx:latest 4 | container_name: nginx 5 | volumes: 6 | - ./nginx/conf.d:/etc/nginx/conf.d:ro 7 | - ../frontend/build/client:/home/isucon/webapp/public:ro 8 | ports: 9 | - "8080:80" 10 | depends_on: 11 | - webapp 12 | webapp: 13 | build: 14 | context: ../webapp/python 15 | dockerfile: ../../development/dockerfiles/Dockerfile.python 16 | image: webapp-python 17 | expose: 18 | - 8080 19 | volumes: 20 | - ../webapp/sql:/home/isucon/webapp/sql 21 | working_dir: /home/isucon/webapp/python 22 | depends_on: 23 | db: 24 | condition: service_healthy 25 | environment: 26 | ISUCON_DB_HOST: db 27 | extra_hosts: 28 | - "host.docker.internal:host-gateway" 29 | db: 30 | image: public.ecr.aws/docker/library/mysql:8 31 | environment: 32 | ENV: local-dev 33 | MYSQL_ROOT_PASSWORD: isucon 34 | MYSQL_DATABASE: isuride 35 | ports: 36 | - 3306:3306 37 | volumes: 38 | - ./mysql/db:/var/lib/mysql 39 | - ../webapp/sql:/docker-entrypoint-initdb.d 40 | healthcheck: 41 | test: mysqladmin ping -h 127.0.0.1 -uisucon -pisucon 42 | start_period: 60s 43 | matcher: 44 | image: mirror.gcr.io/curlimages/curl:latest 45 | command: /bin/sh -c "while true; do curl -s http://nginx/api/internal/matching; sleep 0.5; done" 46 | -------------------------------------------------------------------------------- /webapp/php/src/Database/Model/Ride.php: -------------------------------------------------------------------------------- 1 | toUnixMilliseconds($this->createdAt); 32 | } 33 | 34 | /** 35 | * @throws \DateMalformedStringException 36 | */ 37 | public function updatedAtUnixMilliseconds(): int 38 | { 39 | return $this->toUnixMilliseconds($this->updatedAt); 40 | } 41 | 42 | /** 43 | * @throws \DateMalformedStringException 44 | */ 45 | private function toUnixMilliseconds(string $dateTime): int 46 | { 47 | $dateTimeImmutable = new DateTimeImmutable($dateTime); 48 | $dateTimeImmutable->setTimezone(new DateTimeZone('UTC')); 49 | return (int)$dateTimeImmutable->format('Uv'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /bench/internal/concurrent/simple_slice.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import ( 4 | "iter" 5 | "slices" 6 | "sync" 7 | ) 8 | 9 | type SimpleSlice[V any] struct { 10 | s []V 11 | lock sync.RWMutex 12 | } 13 | 14 | func NewSimpleSlice[V any]() *SimpleSlice[V] { 15 | return &SimpleSlice[V]{ 16 | s: []V{}, 17 | } 18 | } 19 | 20 | func (s *SimpleSlice[V]) Append(value V) { 21 | s.lock.Lock() 22 | defer s.lock.Unlock() 23 | s.s = append(s.s, value) 24 | } 25 | 26 | func (s *SimpleSlice[V]) Len() int { 27 | s.lock.RLock() 28 | defer s.lock.RUnlock() 29 | return len(s.s) 30 | } 31 | 32 | func (s *SimpleSlice[V]) Iter() iter.Seq2[int, V] { 33 | return func(yield func(int, V) bool) { 34 | s.lock.RLock() 35 | defer s.lock.RUnlock() 36 | for i, v := range s.s { 37 | if !yield(i, v) { 38 | break 39 | } 40 | } 41 | } 42 | } 43 | 44 | func (s *SimpleSlice[V]) BackwardIter() iter.Seq2[int, V] { 45 | return func(yield func(int, V) bool) { 46 | s.lock.RLock() 47 | defer s.lock.RUnlock() 48 | for i, v := range slices.Backward(s.s) { 49 | if !yield(i, v) { 50 | break 51 | } 52 | } 53 | } 54 | } 55 | 56 | func (s *SimpleSlice[V]) Values() iter.Seq[V] { 57 | return func(yield func(V) bool) { 58 | s.lock.RLock() 59 | defer s.lock.RUnlock() 60 | for _, v := range s.s { 61 | if !yield(v) { 62 | return 63 | } 64 | } 65 | } 66 | } 67 | 68 | func (s *SimpleSlice[V]) ToSlice() []V { 69 | return slices.Collect(s.Values()) 70 | } 71 | -------------------------------------------------------------------------------- /frontend/app/components/modules/price/price.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps, FC } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { MoneyIcon } from "~/components/icon/money"; 4 | import { Text } from "~/components/primitives/text/text"; 5 | import { PriceText } from "../price-text/price-text"; 6 | 7 | type PriceTextProps = { 8 | value: number; 9 | discount?: number; 10 | pre?: string; 11 | } & ComponentProps<"div">; 12 | 13 | export const Price: FC = ({ 14 | value, 15 | pre, 16 | discount, 17 | className, 18 | ...props 19 | }) => { 20 | return ( 21 |
    28 |
    29 | 30 | {pre && ( 31 | 32 | {pre}: 33 | 34 | )} 35 | 36 |
    37 | {!!discount && ( 38 | 43 | ( 44 | 45 | 割引額: 46 | 47 | ) 48 | 49 | )} 50 |
    51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /bench/benchrun/proto/isuxportal/services/registration/session.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package isuxportal.proto.services.registration; 3 | 4 | 5 | import "isuxportal/resources/team.proto"; 6 | import "isuxportal/resources/coupon.proto"; 7 | import "isuxportal/resources/env_check.proto"; 8 | 9 | // query parameter 10 | message GetRegistrationSessionQuery { 11 | int64 team_id = 1; 12 | string invite_token = 2; 13 | string bypass_token = 16; 14 | } 15 | 16 | message GetRegistrationSessionResponse { 17 | isuxportal.proto.resources.Team team = 1; 18 | enum Status { 19 | CLOSED = 0; 20 | NOT_JOINABLE = 1; 21 | NOT_LOGGED_IN = 2; 22 | CREATABLE = 3; 23 | JOINABLE = 4; 24 | JOINED = 5; 25 | DISQUALIFIED = 6; 26 | } 27 | Status status = 2; 28 | string github_login = 3; 29 | string github_avatar_url = 4; 30 | string discord_tag = 5; 31 | string discord_avatar_url = 6; 32 | string member_invite_url = 7; 33 | string discord_server_id = 8; 34 | isuxportal.proto.resources.EnvCheckStatus env_check_status = 9; 35 | isuxportal.proto.resources.Coupon coupon = 10; 36 | } 37 | 38 | message UpdateRegistrationRequest { 39 | string team_name = 1; 40 | string name = 2; // contestant name 41 | string email_address = 3; 42 | bool is_student = 4; 43 | bool is_in_person = 5; 44 | string avatar_url = 6; 45 | } 46 | 47 | message UpdateRegistrationResponse {} 48 | 49 | message DeleteRegistrationRequest {} 50 | 51 | message DeleteRegistrationResponse {} 52 | -------------------------------------------------------------------------------- /development/compose-perl.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | image: public.ecr.aws/docker/library/nginx:latest 4 | container_name: nginx 5 | volumes: 6 | - ./nginx/conf.d:/etc/nginx/conf.d:ro 7 | - ../frontend/build/client:/home/isucon/webapp/public:ro 8 | ports: 9 | - "8080:80" 10 | depends_on: 11 | - webapp 12 | webapp: 13 | build: 14 | context: ../webapp/perl 15 | dockerfile: ../../development/dockerfiles/Dockerfile.perl 16 | expose: 17 | - 8080 18 | volumes: 19 | - ../webapp/sql:/home/isucon/webapp/sql 20 | working_dir: /home/isucon/webapp/perl 21 | depends_on: 22 | db: 23 | condition: service_healthy 24 | environment: 25 | ISUCON_DB_HOST: db 26 | MOJO_MODE: deployment 27 | PLACK_ENV: deployment 28 | extra_hosts: 29 | - "host.docker.internal:host-gateway" 30 | db: 31 | image: public.ecr.aws/docker/library/mysql:8 32 | environment: 33 | ENV: local-dev 34 | MYSQL_ROOT_PASSWORD: isucon 35 | MYSQL_DATABASE: isucon 36 | ports: 37 | - 3306:3306 38 | volumes: 39 | - ./mysql/db:/var/lib/mysql 40 | - ../webapp/sql:/docker-entrypoint-initdb.d 41 | healthcheck: 42 | test: mysqladmin ping -h 127.0.0.1 -uisucon -pisucon 43 | start_period: 60s 44 | matcher: 45 | image: mirror.gcr.io/curlimages/curl:latest 46 | command: /bin/sh -c "while true; do curl -s http://nginx/api/internal/matching; sleep 0.5; done" 47 | -------------------------------------------------------------------------------- /provisioning/ansible/roles/nginx/files/etc/nginx/sites-available/isuride.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | server_name _; 4 | index index.html index.htm index.nginx-debian.html; 5 | root /var/www/html; 6 | location / { 7 | try_files $uri $uri/ =404; 8 | } 9 | } 10 | 11 | server { 12 | listen 443 ssl default_server; 13 | server_name _; 14 | index index.html index.htm index.nginx-debian.html; 15 | root /var/www/html; 16 | 17 | # bot避けのためのvhostで、この証明書は自己署名です 18 | ssl_certificate /etc/nginx/tls/dummy.crt; 19 | ssl_certificate_key /etc/nginx/tls/dummy.key; 20 | ssl_protocols TLSv1.3; 21 | ssl_prefer_server_ciphers off; 22 | 23 | location / { 24 | try_files $uri $uri/ =404; 25 | } 26 | } 27 | 28 | server { 29 | listen 443 ssl; 30 | server_name xiv.isucon.net; 31 | server_name *.xiv.isucon.net; 32 | 33 | ssl_certificate /etc/nginx/tls/_.xiv.isucon.net.crt; 34 | ssl_certificate_key /etc/nginx/tls/_.xiv.isucon.net.key; 35 | 36 | ssl_protocols TLSv1.3; 37 | ssl_prefer_server_ciphers off; 38 | 39 | client_max_body_size 10m; 40 | root /home/isucon/webapp/public/; 41 | location / { 42 | try_files $uri /index.html; 43 | } 44 | location /api/ { 45 | proxy_set_header Host $host; 46 | proxy_pass http://localhost:8080; 47 | } 48 | 49 | location /api/internal/ { 50 | # localhostからのみアクセスを許可 51 | allow 127.0.0.1; 52 | deny all; 53 | proxy_set_header Host $host; 54 | proxy_pass http://localhost:8080; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /frontend/app/components/modules/location-button/location-button.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps, FC } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { Coordinate } from "~/api/api-schemas"; 4 | import { PinIcon } from "~/components/icon/pin"; 5 | import { Button } from "~/components/primitives/button/button"; 6 | 7 | type Direction = "from" | "to"; 8 | 9 | type LocationButtonProps = { 10 | direction?: Direction; 11 | location?: Coordinate; 12 | label?: string; 13 | disabled?: boolean; 14 | className?: string; 15 | placeholder?: string; 16 | onClick?: () => void; 17 | } & ComponentProps; 18 | 19 | export const LocationButton: FC = ({ 20 | direction, 21 | location, 22 | label, 23 | disabled, 24 | className, 25 | onClick, 26 | placeholder = "場所を選択する", 27 | ...props 28 | }) => { 29 | return ( 30 | 50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /development/compose-node.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | image: public.ecr.aws/docker/library/nginx:latest 4 | container_name: nginx 5 | volumes: 6 | - ./nginx/conf.d:/etc/nginx/conf.d:ro 7 | - ../frontend/build/client:/home/isucon/webapp/public:ro 8 | ports: 9 | - "8080:80" 10 | depends_on: 11 | - webapp 12 | webapp: 13 | build: 14 | context: ../webapp/nodejs 15 | dockerfile: ../../development/dockerfiles/Dockerfile.node 16 | image: webapp-nodejs 17 | expose: 18 | - 8080 19 | volumes: 20 | - ../webapp/sql:/home/isucon/webapp/sql 21 | working_dir: /home/isucon/webapp/nodejs 22 | depends_on: 23 | db: 24 | condition: service_healthy 25 | environment: 26 | ISUCON_DB_HOST: db 27 | extra_hosts: 28 | - "host.docker.internal:host-gateway" 29 | healthcheck: 30 | test: ["CMD", "curl", "http://webapp:8080/no_exist"] 31 | db: 32 | image: public.ecr.aws/docker/library/mysql:8 33 | environment: 34 | ENV: local-dev 35 | MYSQL_ROOT_PASSWORD: isucon 36 | MYSQL_DATABASE: isucon 37 | ports: 38 | - 3306:3306 39 | volumes: 40 | - ./mysql/db:/var/lib/mysql 41 | - ../webapp/sql:/docker-entrypoint-initdb.d 42 | healthcheck: 43 | test: mysqladmin ping -h 127.0.0.1 -uisucon -pisucon 44 | start_period: 60s 45 | matcher: 46 | image: mirror.gcr.io/curlimages/curl:latest 47 | command: /bin/sh -c "while true; do curl -s http://nginx/api/internal/matching; sleep 0.5; done" 48 | -------------------------------------------------------------------------------- /webapp/perl/lib/Kossy/Isuride/Handler/Internal.pm: -------------------------------------------------------------------------------- 1 | package Kossy::Isuride::Handler::Internal; 2 | use v5.40; 3 | use utf8; 4 | 5 | use HTTP::Status qw(:constants); 6 | 7 | # このAPIをインスタンス内から一定間隔で叩かせることで、椅子とライドをマッチングさせる 8 | sub internal_get_matching($app, $c) { 9 | # MEMO: 一旦最も待たせているリクエストに適当な空いている椅子マッチさせる実装とする。おそらくもっといい方法があるはず… 10 | my $ride = $app->dbh->select_row('SELECT * FROM rides WHERE chair_id IS NULL ORDER BY created_at LIMIT 1'); 11 | 12 | unless (defined $ride) { 13 | return $c->halt_no_content(HTTP_NO_CONTENT); 14 | } 15 | 16 | my $matched; 17 | my $empty = false; 18 | 19 | for (1 .. 10) { 20 | $matched = $app->dbh->select_row('SELECT * FROM chairs INNER JOIN (SELECT id FROM chairs WHERE is_active = TRUE ORDER BY RAND() LIMIT 1) AS tmp ON chairs.id = tmp.id LIMIT 1'); 21 | 22 | unless (defined $matched) { 23 | return $c->halt_no_content(HTTP_NO_CONTENT); 24 | } 25 | 26 | $empty = $app->dbh->select_one("SELECT COUNT(*) = 0 FROM (SELECT COUNT(chair_sent_at) = 6 AS completed FROM ride_statuses WHERE ride_id IN (SELECT id FROM rides WHERE chair_id = ?) GROUP BY ride_id) is_completed WHERE completed = FALSE", $matched->{id}); 27 | 28 | if ($empty) { 29 | last; 30 | } 31 | } 32 | 33 | if (!$empty) { 34 | return $c->halt_no_content(HTTP_NO_CONTENT); 35 | } 36 | 37 | $app->dbh->query('UPDATE rides SET chair_id = ? WHERE id = ?', $matched->{id}, $ride->{id}); 38 | 39 | return $c->halt_no_content(HTTP_NO_CONTENT); 40 | } 41 | -------------------------------------------------------------------------------- /webapp/perl/lib/Mojo/Isuride/Handler/Internal.pm: -------------------------------------------------------------------------------- 1 | package Mojo::Isuride::Handler::Internal; 2 | use v5.40; 3 | use utf8; 4 | 5 | use HTTP::Status qw(:constants); 6 | 7 | # このAPIをインスタンス内から一定間隔で叩かせることで、椅子とライドをマッチングさせる 8 | sub internal_get_matching($c) { 9 | my $db = $c->mysql->db; 10 | 11 | # MEMO: 一旦最も待たせているリクエストに適当な空いている椅子マッチさせる実装とする。おそらくもっといい方法があるはず… 12 | my $ride = $db->select_row('SELECT * FROM rides WHERE chair_id IS NULL ORDER BY created_at LIMIT 1'); 13 | 14 | unless (defined $ride) { 15 | return $c->halt_no_content(HTTP_NO_CONTENT); 16 | } 17 | 18 | my $matched; 19 | my $empty = false; 20 | 21 | for (1 .. 10) { 22 | $matched = $db->select_row('SELECT * FROM chairs INNER JOIN (SELECT id FROM chairs WHERE is_active = TRUE ORDER BY RAND() LIMIT 1) AS tmp ON chairs.id = tmp.id LIMIT 1'); 23 | 24 | unless (defined $matched) { 25 | return $c->halt_no_content(HTTP_NO_CONTENT); 26 | } 27 | 28 | $empty = $db->select_one("SELECT COUNT(*) = 0 FROM (SELECT COUNT(chair_sent_at) = 6 AS completed FROM ride_statuses WHERE ride_id IN (SELECT id FROM rides WHERE chair_id = ?) GROUP BY ride_id) is_completed WHERE completed = FALSE", $matched->{id}); 29 | 30 | if ($empty) { 31 | last; 32 | } 33 | } 34 | 35 | if (!$empty) { 36 | return $c->halt_no_content(HTTP_NO_CONTENT); 37 | } 38 | 39 | $db->query('UPDATE rides SET chair_id = ? WHERE id = ?', $matched->{id}, $ride->{id}); 40 | 41 | return $c->halt_no_content(HTTP_NO_CONTENT); 42 | } 43 | -------------------------------------------------------------------------------- /frontend/app/routes/client_.register-payment/route.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction } from "@remix-run/node"; 2 | import { ClientActionFunctionArgs, Form, redirect } from "@remix-run/react"; 3 | import { fetchAppPostPaymentMethods } from "~/api/api-components"; 4 | import { Button } from "~/components/primitives/button/button"; 5 | import { TextInput } from "~/components/primitives/form/text-input"; 6 | import { FormFrame } from "~/components/primitives/frame/form-frame"; 7 | 8 | export const meta: MetaFunction = () => { 9 | return [ 10 | { title: "Regiter Payment | ISURIDE" }, 11 | { name: "description", content: "決済トークン登録" }, 12 | ]; 13 | }; 14 | 15 | export const clientAction = async ({ request }: ClientActionFunctionArgs) => { 16 | const formData = await request.formData(); 17 | await fetchAppPostPaymentMethods({ 18 | body: { 19 | token: String(formData.get("payment-token")), 20 | }, 21 | }); 22 | return redirect(`/client`); 23 | }; 24 | 25 | export default function ClientRegister() { 26 | return ( 27 | 28 |

    決済トークン登録

    29 |
    30 |
    31 | 37 |
    38 | 41 |
    42 |
    43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /bench/benchmarker/scenario/validation.go: -------------------------------------------------------------------------------- 1 | package scenario 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log/slog" 7 | "time" 8 | 9 | "github.com/isucon/isucandar" 10 | ) 11 | 12 | // Validation はシナリオの結果検証処理を行う 13 | func (s *Scenario) Validation(ctx context.Context, step *isucandar.BenchmarkStep) error { 14 | // 負荷走行終了後、payment server へのリクエストが届くかもしれないので5秒だけ待つ 15 | time.Sleep(5 * time.Second) 16 | s.paymentServer.Close() 17 | s.sendResultWait.Wait() 18 | 19 | for _, region := range s.world.Regions { 20 | s.contestantLogger.Info("最終地域情報", 21 | slog.String("名前", region.Name), 22 | slog.Int("ユーザー登録数", region.UsersDB.Len()), 23 | slog.Int("アクティブユーザー数", region.ActiveUserNum()), 24 | ) 25 | } 26 | for _, owner := range s.world.OwnerDB.Iter() { 27 | s.contestantLogger.Info("最終オーナー情報", 28 | slog.String("名前", owner.RegisteredData.Name), 29 | slog.Int64("売上", owner.TotalSales.Load()), 30 | slog.Int("椅子数", owner.ChairDB.Len()), 31 | ) 32 | } 33 | if s.completedRequests > 0 { 34 | s.contestantLogger.Info(fmt.Sprintf("%.1f%%のライドは椅子がマッチされるまでの時間、%.1f%%のライドはマッチされた椅子が乗車地点までに掛かる時間、%.1f%%のライドは椅子の実移動時間に不満がありました", 35 | (1-float64(s.evaluationMap[0])/float64(s.completedRequests))*100, 36 | (1-float64(s.evaluationMap[1])/float64(s.completedRequests))*100, 37 | (1-float64(s.evaluationMap[2]+s.evaluationMap[3])/float64(s.completedRequests*2))*100, 38 | )) 39 | } 40 | s.contestantLogger.Info("結果", slog.Bool("pass", !s.failed), slog.Int64("スコア", s.Score(true)), slog.Any("種別エラー数", s.world.ErrorCounter.Count())) 41 | return sendResult(s, true, !s.failed) 42 | } 43 | -------------------------------------------------------------------------------- /bench/.gitignore: -------------------------------------------------------------------------------- 1 | ### Go template 2 | # If you prefer the allow list template instead of the deny list, see community template: 3 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 4 | # 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | 21 | # Go workspace file 22 | go.work 23 | 24 | ### Windows template 25 | # Windows thumbnail cache files 26 | Thumbs.db 27 | Thumbs.db:encryptable 28 | ehthumbs.db 29 | ehthumbs_vista.db 30 | 31 | # Dump file 32 | *.stackdump 33 | 34 | # Folder config file 35 | [Dd]esktop.ini 36 | 37 | # Recycle Bin used on file shares 38 | $RECYCLE.BIN/ 39 | 40 | # Windows Installer files 41 | *.cab 42 | *.msi 43 | *.msix 44 | *.msm 45 | *.msp 46 | 47 | # Windows shortcuts 48 | *.lnk 49 | 50 | ### macOS template 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | bench 79 | -------------------------------------------------------------------------------- /webapp/python/app/utils.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import os 3 | from datetime import datetime, timedelta 4 | 5 | from .models import Ride 6 | 7 | INITIAL_FARE = 500 8 | FARE_PER_DISTANCE = 100 9 | 10 | EPOCH = datetime(1970, 1, 1) 11 | 12 | 13 | def secure_random_str(b: int) -> str: 14 | random_bytes: bytes = os.urandom(b) 15 | return binascii.hexlify(random_bytes).decode("utf-8") 16 | 17 | 18 | def timestamp_millis(dt: datetime) -> int: 19 | return (dt - EPOCH) // timedelta(milliseconds=1) 20 | 21 | 22 | def datetime_fromtimestamp_millis(t: int) -> datetime: 23 | return EPOCH + timedelta(milliseconds=t) 24 | 25 | 26 | def calculate_fare( 27 | pickup_latitude: int, pickup_longitude: int, dest_latitude: int, dest_longitude: int 28 | ) -> int: 29 | metered_fare = FARE_PER_DISTANCE * calculate_distance( 30 | pickup_latitude, pickup_longitude, dest_latitude, dest_longitude 31 | ) 32 | return INITIAL_FARE + metered_fare 33 | 34 | 35 | # マンハッタン距離を求める 36 | def calculate_distance( 37 | a_latitude: int, a_longitude: int, b_latitude: int, b_longitude: int 38 | ) -> int: 39 | return abs(a_latitude - b_latitude) + abs(a_longitude - b_longitude) 40 | 41 | 42 | def calculate_sale(ride: Ride) -> int: 43 | return calculate_fare( 44 | ride.pickup_latitude, 45 | ride.pickup_longitude, 46 | ride.destination_latitude, 47 | ride.destination_longitude, 48 | ) 49 | 50 | 51 | def sum_sales(rides: list[Ride]) -> int: 52 | sale = 0 53 | for ride in rides: 54 | sale += calculate_sale(ride) 55 | return sale 56 | -------------------------------------------------------------------------------- /webapp/go/.gitignore: -------------------------------------------------------------------------------- 1 | ### Go template 2 | # If you prefer the allow list template instead of the deny list, see community template: 3 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 4 | # 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | 21 | # Go workspace file 22 | go.work 23 | 24 | ### Windows template 25 | # Windows thumbnail cache files 26 | Thumbs.db 27 | Thumbs.db:encryptable 28 | ehthumbs.db 29 | ehthumbs_vista.db 30 | 31 | # Dump file 32 | *.stackdump 33 | 34 | # Folder config file 35 | [Dd]esktop.ini 36 | 37 | # Recycle Bin used on file shares 38 | $RECYCLE.BIN/ 39 | 40 | # Windows Installer files 41 | *.cab 42 | *.msi 43 | *.msix 44 | *.msm 45 | *.msp 46 | 47 | # Windows shortcuts 48 | *.lnk 49 | 50 | ### macOS template 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | isuride 79 | -------------------------------------------------------------------------------- /frontend/app/components/primitives/text/text.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentPropsWithoutRef, FC, PropsWithChildren } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | type Size = "2xl" | "xl" | "lg" | "sm" | "xs"; 5 | 6 | type Variant = "danger" | "normal"; 7 | 8 | type Tag = "p" | "span" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; 9 | 10 | type TextProps = PropsWithChildren<{ 11 | tagName?: Tag; 12 | bold?: boolean; 13 | size?: Size; 14 | variant?: Variant; 15 | className?: string; 16 | }> & 17 | ComponentPropsWithoutRef; 18 | 19 | const getSizeClass = (size?: Size) => { 20 | switch (size) { 21 | case "2xl": 22 | return "text-2xl"; 23 | case "xl": 24 | return "text-xl"; 25 | case "lg": 26 | return "text-lg"; 27 | case "sm": 28 | return "text-sm"; 29 | case "xs": 30 | return "text-xs"; 31 | default: 32 | return ""; 33 | } 34 | }; 35 | 36 | const getVariantClass = (variant?: Variant) => { 37 | switch (variant) { 38 | case "danger": 39 | return "text-red-500"; 40 | default: 41 | return ""; 42 | } 43 | }; 44 | 45 | export const Text: FC = ({ 46 | tagName = "p", 47 | bold, 48 | size, 49 | variant, 50 | className, 51 | children, 52 | ...props 53 | }) => { 54 | const Tag = tagName; 55 | return ( 56 | 65 | {children} 66 | 67 | ); 68 | }; 69 | --------------------------------------------------------------------------------