├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------