├── portal ├── log │ └── .keep ├── app │ ├── models │ │ ├── .keep │ │ ├── concerns │ │ │ └── .keep │ │ ├── term.rb │ │ ├── team.rb │ │ ├── user.rb │ │ ├── job.rb │ │ └── score.rb │ ├── mailers │ │ └── .keep │ ├── assets │ │ ├── images │ │ │ └── .keep │ │ ├── stylesheets │ │ │ ├── devise.scss │ │ │ ├── home.scss │ │ │ ├── jobs.scss │ │ │ └── application.scss │ │ ├── config │ │ │ └── manifest.js │ │ └── javascripts │ │ │ ├── home.coffee │ │ │ ├── jobs.coffee │ │ │ └── application.js │ ├── controllers │ │ ├── concerns │ │ │ └── .keep │ │ ├── application_controller.rb │ │ ├── jobs_controller.rb │ │ └── home_controller.rb │ ├── helpers │ │ ├── home_helper.rb │ │ ├── jobs_helper.rb │ │ └── application_helper.rb │ ├── views │ │ ├── devise │ │ │ ├── mailer │ │ │ │ ├── password_change.html.erb │ │ │ │ ├── confirmation_instructions.html.erb │ │ │ │ ├── unlock_instructions.html.erb │ │ │ │ └── reset_password_instructions.html.erb │ │ │ ├── unlocks │ │ │ │ └── new.html.erb │ │ │ ├── passwords │ │ │ │ ├── new.html.erb │ │ │ │ └── edit.html.erb │ │ │ ├── confirmations │ │ │ │ └── new.html.erb │ │ │ ├── sessions │ │ │ │ └── new.html.erb │ │ │ ├── registrations │ │ │ │ ├── new.html.erb │ │ │ │ └── edit.html.erb │ │ │ └── shared │ │ │ │ └── _links.html.erb │ │ ├── layouts │ │ │ ├── devise.html.erb │ │ │ └── application.html.erb │ │ └── home │ │ │ └── index.erb │ └── jobs │ │ └── benchmarker_job.rb ├── lib │ ├── assets │ │ └── .keep │ └── tasks │ │ └── .keep ├── public │ ├── favicon.ico │ ├── robots.txt │ ├── 500.html │ ├── 422.html │ └── 404.html ├── .bundle │ └── config ├── config │ ├── sidekiq.yml │ ├── boot.rb │ ├── initializers │ │ ├── cookies_serializer.rb │ │ ├── session_store.rb │ │ ├── mime_types.rb │ │ ├── filter_parameter_logging.rb │ │ ├── sidekiq.rb │ │ ├── backtrace_silencers.rb │ │ ├── assets.rb │ │ ├── wrap_parameters.rb │ │ └── inflections.rb │ ├── environment.rb │ ├── deploy │ │ └── production.rb │ ├── locales │ │ ├── en.yml │ │ └── devise.en.yml │ ├── database.yml │ ├── secrets.yml │ ├── application.rb │ ├── environments │ │ ├── development.rb │ │ ├── test.rb │ │ └── production.rb │ ├── deploy.rb │ └── routes.rb ├── bin │ ├── bundle │ ├── rake │ ├── rails │ ├── spring │ └── setup ├── config.ru ├── db │ ├── migrate │ │ ├── 20160310142520_create_teams.rb │ │ ├── 20160310144552_create_jobs.rb │ │ ├── 20160310141714_create_terms.rb │ │ ├── 20160319030401_create_scores.rb │ │ └── 20160310140544_devise_create_users.rb │ ├── seeds.rb │ └── schema.rb ├── Rakefile ├── README.md ├── Gemfile.org └── Capfile ├── webapp ├── sql │ └── .gitignore ├── golang │ ├── .gitignore │ ├── setup.sh │ ├── Makefile │ ├── templates │ │ ├── post_id.html │ │ ├── posts.html │ │ ├── user.html │ │ ├── banned.html │ │ ├── register.html │ │ ├── login.html │ │ ├── index.html │ │ ├── layout.html │ │ └── post.html │ ├── go.mod │ ├── Dockerfile │ └── go.sum ├── python │ ├── .dockerignore │ ├── .gitignore │ ├── templates │ │ ├── posts.html │ │ ├── layout.html │ │ ├── user.html │ │ ├── header.html │ │ ├── banned.html │ │ ├── register.html │ │ ├── login.html │ │ ├── index.html │ │ └── post.html │ ├── pyproject.toml │ └── Dockerfile ├── ruby │ ├── .gitignore │ ├── unicorn_config.rb │ ├── views │ │ ├── posts.erb │ │ ├── user.erb │ │ ├── layout.erb │ │ ├── banned.erb │ │ ├── header.erb │ │ ├── register.erb │ │ ├── login.erb │ │ ├── index.erb │ │ └── post.erb │ ├── Gemfile │ ├── config.ru │ ├── Dockerfile │ └── Gemfile.lock ├── node │ ├── .gitignore │ ├── views │ │ ├── post.ejs │ │ ├── footer.ejs │ │ ├── posts.ejs │ │ ├── header.ejs │ │ ├── user.ejs │ │ ├── page_header.ejs │ │ ├── banned.ejs │ │ ├── register.ejs │ │ ├── login.ejs │ │ ├── index.ejs │ │ └── _post.ejs │ ├── src │ │ └── types │ │ │ └── express-session.d.ts │ ├── tsconfig.json │ ├── Dockerfile │ └── package.json ├── public │ ├── favicon.ico │ ├── img │ │ └── ajax-loader.gif │ ├── js │ │ ├── timeago.min.js │ │ └── main.js │ └── css │ │ └── style.css ├── php │ ├── views │ │ ├── posts.php │ │ ├── user.php │ │ ├── layout.php │ │ ├── banned.php │ │ ├── header.php │ │ ├── register.php │ │ ├── login.php │ │ ├── index.php │ │ └── post.php │ ├── composer.json │ └── Dockerfile ├── etc │ └── nginx │ │ └── conf.d │ │ ├── default.conf │ │ └── php.conf.org └── docker-compose.yml ├── ansible_old ├── .gitignore ├── README.md ├── roles │ ├── bench │ │ ├── tasks │ │ │ ├── main.yml │ │ │ ├── go.yml │ │ │ └── benchmarker.yml │ │ └── vars │ │ │ └── main.yml │ ├── portal │ │ ├── handlers │ │ │ ├── mysql.yml │ │ │ ├── main.yml │ │ │ ├── nginx.yml │ │ │ └── redis.yml │ │ ├── tasks │ │ │ ├── main.yml │ │ │ ├── redis.yml │ │ │ ├── nginx.yml │ │ │ └── mysql.yml │ │ └── files │ │ │ ├── nginx │ │ │ └── shanai-isucon-portal │ │ │ └── mysql │ │ │ └── my.cnf │ └── isucon-base │ │ ├── tasks │ │ ├── git.yml │ │ ├── main.yml │ │ ├── packages.yml │ │ ├── env.yml │ │ ├── user.yml │ │ └── ruby.yml │ │ └── vars │ │ └── main.yml ├── setup-bench.yml ├── setup-portal.yml ├── production ├── group_vars │ └── admin.yml └── setup.yml ├── benchmarker ├── .gitignore ├── run.sh ├── version.go ├── userdata │ ├── .gitignore │ ├── README.md │ ├── ramen │ │ └── ramen.js │ └── load.rb ├── Makefile ├── main.go ├── go.mod ├── util │ └── util.go ├── cli_test.go ├── score │ ├── score.go │ └── fail.go ├── sql │ └── schema.sql ├── Dockerfile ├── cache │ └── cache.go ├── userdata.go └── checker │ └── session.go ├── .github ├── CODEOWNERS └── workflows │ ├── renovate-config-validator-ci.yml │ ├── hadolint.yml │ └── codeql.yml ├── provisioning ├── hosts ├── ansible.cfg ├── bench │ ├── files │ │ └── etc │ │ │ └── profile.d │ │ │ └── bashrc │ └── ansible │ │ ├── 05_build.yml │ │ ├── playbooks.yml │ │ ├── 00_base.yml │ │ ├── 04_userdata.yml │ │ ├── 03_bench.yml │ │ ├── 01_user.yml │ │ ├── 06_kernel.yml │ │ └── 02_golang.yml ├── image │ ├── files │ │ ├── etc │ │ │ ├── nginx │ │ │ │ └── sites-available │ │ │ │ │ ├── isucon.conf │ │ │ │ │ └── isucon-php.conf │ │ │ ├── profile.d │ │ │ │ └── bashrc │ │ │ └── systemd │ │ │ │ └── system │ │ │ │ ├── isu-node.service │ │ │ │ ├── isu-go.service │ │ │ │ ├── isu-python.service │ │ │ │ └── isu-ruby.service │ │ └── home │ │ │ └── isucon │ │ │ └── env.sh │ └── ansible │ │ ├── 09_build_benchmarker.yml │ │ ├── 06_createdb.yml │ │ ├── playbooks.yml │ │ ├── 02_mysql.yml │ │ ├── 08_userdata.yml │ │ ├── 01_user.yml │ │ ├── 03_nginx.yml │ │ ├── 05_app.yml │ │ ├── 00_base.yml │ │ └── 04_xbuild.yml └── README.md ├── .hadolint.yaml ├── .gitignore ├── Makefile ├── LICENSE ├── renovate.json ├── public_manual.md └── AGENTS.md /portal/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /portal/app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /portal/lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /portal/lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /portal/app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /portal/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapp/sql/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /portal/app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /portal/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapp/golang/.gitignore: -------------------------------------------------------------------------------- 1 | app 2 | -------------------------------------------------------------------------------- /ansible_old/.gitignore: -------------------------------------------------------------------------------- 1 | *.retry 2 | -------------------------------------------------------------------------------- /portal/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapp/python/.dockerignore: -------------------------------------------------------------------------------- 1 | .venv 2 | -------------------------------------------------------------------------------- /webapp/python/.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | -------------------------------------------------------------------------------- /benchmarker/.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | bin/ 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | webapp/python/ @kyoto7250 2 | -------------------------------------------------------------------------------- /webapp/ruby/.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | vendor 3 | -------------------------------------------------------------------------------- /benchmarker/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec "$@" 4 | -------------------------------------------------------------------------------- /webapp/golang/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | make 4 | -------------------------------------------------------------------------------- /webapp/node/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /portal/.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: "vendor/bundle" 3 | -------------------------------------------------------------------------------- /portal/app/helpers/home_helper.rb: -------------------------------------------------------------------------------- 1 | module HomeHelper 2 | end 3 | -------------------------------------------------------------------------------- /portal/app/helpers/jobs_helper.rb: -------------------------------------------------------------------------------- 1 | module JobsHelper 2 | end 3 | -------------------------------------------------------------------------------- /portal/app/models/term.rb: -------------------------------------------------------------------------------- 1 | class Term < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /portal/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /provisioning/hosts: -------------------------------------------------------------------------------- 1 | [guests] 2 | isu-app 3 | 4 | [bench] 5 | isu-bench 6 | -------------------------------------------------------------------------------- /provisioning/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | allow_world_readable_tmpfiles = true 3 | -------------------------------------------------------------------------------- /portal/config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: 1 3 | :pidfile: tmp/pids/sidekiq.pid 4 | -------------------------------------------------------------------------------- /webapp/golang/Makefile: -------------------------------------------------------------------------------- 1 | all: app 2 | 3 | app: *.go go.mod go.sum 4 | go build -o app 5 | -------------------------------------------------------------------------------- /.hadolint.yaml: -------------------------------------------------------------------------------- 1 | ignored: 2 | - DL3015 3 | - DL3008 4 | - DL3018 5 | - DL3013 6 | - DL3042 7 | -------------------------------------------------------------------------------- /ansible_old/README.md: -------------------------------------------------------------------------------- 1 | ## setup 2 | 3 | ``` 4 | $ ansible-playbook -i production setup.yml 5 | ``` 6 | -------------------------------------------------------------------------------- /ansible_old/roles/bench/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: go.yml 3 | - include: benchmarker.yml 4 | -------------------------------------------------------------------------------- /webapp/ruby/unicorn_config.rb: -------------------------------------------------------------------------------- 1 | worker_processes 1 2 | preload_app true 3 | listen "0.0.0.0:8080" 4 | -------------------------------------------------------------------------------- /webapp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catatsuy/private-isu/HEAD/webapp/public/favicon.ico -------------------------------------------------------------------------------- /benchmarker/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const Name string = "benchmarker" 4 | const Version string = "0.1.0" 5 | -------------------------------------------------------------------------------- /webapp/golang/templates/post_id.html: -------------------------------------------------------------------------------- 1 | {{ define "content" }} 2 | {{ template "post.html" .Post }} 3 | {{ end }} 4 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/handlers/mysql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart mysql 3 | shell: systemctl restart mysql.service 4 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: mysql.yml 3 | - include: redis.yml 4 | - include: nginx.yml 5 | -------------------------------------------------------------------------------- /webapp/public/img/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catatsuy/private-isu/HEAD/webapp/public/img/ajax-loader.gif -------------------------------------------------------------------------------- /ansible_old/roles/isucon-base/tasks/git.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: be sure git is installed 3 | apt: name=git state=installed 4 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: nginx.yml 3 | - include: redis.yml 4 | - include: mysql.yml 5 | -------------------------------------------------------------------------------- /benchmarker/userdata/.gitignore: -------------------------------------------------------------------------------- 1 | img/ 2 | img.zip 3 | dump.sql 4 | dump.sql.bz2 5 | node_modules/ 6 | .bundle 7 | vendor/ 8 | -------------------------------------------------------------------------------- /webapp/node/views/post.ejs: -------------------------------------------------------------------------------- 1 | <%- include('header.ejs') %> 2 | <%- include('_post.ejs') %> 3 | <%- include('footer.ejs') %> 4 | -------------------------------------------------------------------------------- /ansible_old/roles/bench/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | golang_version: 1.16.3 3 | golang_os: linux 4 | golang_arch: amd64 5 | gopath: /opt/go 6 | -------------------------------------------------------------------------------- /benchmarker/Makefile: -------------------------------------------------------------------------------- 1 | all: bin/benchmarker 2 | 3 | bin/benchmarker: *.go checker/*.go go.mod go.sum 4 | go build -o bin/benchmarker 5 | -------------------------------------------------------------------------------- /portal/app/models/team.rb: -------------------------------------------------------------------------------- 1 | class Team < ActiveRecord::Base 2 | has_many :users 3 | has_many :scores 4 | has_many :jobs 5 | end 6 | -------------------------------------------------------------------------------- /portal/app/assets/stylesheets/devise.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | 3 | .centered { 4 | @include center-block(); 5 | width: 320px; 6 | } 7 | -------------------------------------------------------------------------------- /webapp/golang/templates/posts.html: -------------------------------------------------------------------------------- 1 |
2 | {{ range . }} 3 | {{ template "post.html" . }} 4 | {{ end }} 5 |
6 | -------------------------------------------------------------------------------- /ansible_old/roles/isucon-base/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: packages.yml 3 | - include: user.yml 4 | - include: ruby.yml 5 | - include: env.yml 6 | -------------------------------------------------------------------------------- /portal/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /webapp/python/templates/posts.html: -------------------------------------------------------------------------------- 1 |
2 | {% for post in posts %} 3 | {% include 'post.html' %} 4 | {% endfor %} 5 |
6 | -------------------------------------------------------------------------------- /portal/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /webapp/ruby/views/posts.erb: -------------------------------------------------------------------------------- 1 |
2 | <% posts.each do |post| %> 3 | <%= erb :post, locals: { post: post } %> 4 | <% end %> 5 |
6 | -------------------------------------------------------------------------------- /portal/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /provisioning/bench/files/etc/profile.d/bashrc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -f /etc/bashrc ]; then 3 | . /etc/bashrc 4 | fi 5 | 6 | export PATH=/usr/local/go/bin:$PATH 7 | -------------------------------------------------------------------------------- /webapp/node/views/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ansible_old/setup-bench.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: bench 3 | become: yes 4 | become_user: root 5 | become_method: sudo 6 | roles: 7 | - isucon-base 8 | - bench 9 | -------------------------------------------------------------------------------- /ansible_old/setup-portal.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: portal 3 | become: yes 4 | become_user: root 5 | become_method: sudo 6 | roles: 7 | - isucon-base 8 | - portal 9 | -------------------------------------------------------------------------------- /benchmarker/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "os" 4 | 5 | func main() { 6 | cli := &CLI{outStream: os.Stdout, errStream: os.Stderr} 7 | os.Exit(cli.Run(os.Args)) 8 | } 9 | -------------------------------------------------------------------------------- /webapp/node/views/posts.ejs: -------------------------------------------------------------------------------- 1 |
2 | <% posts.forEach(function(post) { %> 3 | <%- include('_post.ejs', { post: post }) %> 4 | <% }) %> 5 |
6 | -------------------------------------------------------------------------------- /portal/app/views/devise/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

We're contacting you to notify you that your password has been changed.

4 | -------------------------------------------------------------------------------- /portal/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /webapp/php/views/posts.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /ansible_old/roles/isucon-base/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | rbenv_user: isucon 3 | rbenv_ruby_version: 2.7.3 4 | key_github_users: 5 | - st-cyrill 6 | - catatsuy 7 | - edvakf 8 | - walf443 9 | -------------------------------------------------------------------------------- /portal/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /portal/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /ansible_old/production: -------------------------------------------------------------------------------- 1 | [app] 2 | shanai-isucon-app-01 3 | 4 | [portal] 5 | shanai-isucon-portal 6 | 7 | [bench] 8 | shanai-isucon-bench-01 9 | 10 | [admin:children] 11 | portal 12 | bench 13 | -------------------------------------------------------------------------------- /portal/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_isucon6_worker_queue_session' 4 | -------------------------------------------------------------------------------- /portal/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/handlers/nginx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload nginx 3 | shell: /usr/sbin/nginx -t && /etc/init.d/nginx reload 4 | 5 | - name: restart nginx 6 | shell: /usr/sbin/nginx -t && /etc/init.d/nginx restart 7 | -------------------------------------------------------------------------------- /portal/app/assets/stylesheets/home.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the home controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /portal/app/assets/stylesheets/jobs.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the jobs controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/handlers/redis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload redis 3 | shell: /etc/init.d/redis-server reload 4 | 5 | - name: restart redis 6 | shell: /etc/init.d/redis-server stop && /etc/init.d/redis-server start 7 | -------------------------------------------------------------------------------- /portal/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /webapp/etc/nginx/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | client_max_body_size 10m; 5 | root /public/; 6 | 7 | location / { 8 | proxy_set_header Host $host; 9 | proxy_pass http://app:8080; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /provisioning/bench/ansible/05_build.yml: -------------------------------------------------------------------------------- 1 | - hosts: bench 2 | become: yes 3 | become_user: isucon 4 | gather_facts: no 5 | tasks: 6 | - name: build benchmarker 7 | shell: bash -lc 'cd /home/isucon/private_isu.git/benchmarker ; make' 8 | -------------------------------------------------------------------------------- /ansible_old/roles/isucon-base/tasks/packages.yml: -------------------------------------------------------------------------------- 1 | 2 | - name: Install essential packages 3 | apt: name={{ item }} state=latest 4 | with_items: 5 | - libmysqlclient-dev 6 | - nodejs 7 | - nodejs-legacy 8 | - vim 9 | - git 10 | 11 | -------------------------------------------------------------------------------- /portal/app/assets/javascripts/home.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /portal/app/assets/javascripts/jobs.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /portal/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /portal/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /webapp/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | # A sample Gemfile 2 | source "https://rubygems.org" 3 | 4 | gem "sinatra" 5 | gem "sinatra-contrib" 6 | gem "rack" 7 | gem "unicorn" 8 | gem 'bigdecimal' 9 | gem "mysql2" 10 | gem "rack-flash3" 11 | gem 'connection_pool' 12 | gem "dalli" 13 | -------------------------------------------------------------------------------- /ansible_old/group_vars/admin.yml: -------------------------------------------------------------------------------- 1 | --- 2 | mysql_user: isucon 3 | mysql_pass: bfirvuvniinonb 4 | mysql_db: isucon_portal_production 5 | mysql_host: 172.31.0.105 6 | mysql_port: 3306 7 | redis_pass: ub787n98n90fhh 8 | redis_host: 172.31.0.105 9 | redis_port: 6379 10 | -------------------------------------------------------------------------------- /portal/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /webapp/node/src/types/express-session.d.ts: -------------------------------------------------------------------------------- 1 | import 'express-session'; 2 | 3 | declare module 'express-session' { 4 | interface SessionData { 5 | /** ログインユーザー ID */ 6 | userId?: number; 7 | /** CSRF トークン */ 8 | csrfToken?: string; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /portal/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | # Include default devise modules. Others available are: 3 | # :confirmable, :lockable, :timeoutable and :omniauthable 4 | devise :database_authenticatable, :trackable, :validatable 5 | belongs_to :team 6 | end 7 | -------------------------------------------------------------------------------- /portal/app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

6 | -------------------------------------------------------------------------------- /portal/db/migrate/20160310142520_create_teams.rb: -------------------------------------------------------------------------------- 1 | class CreateTeams < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :teams do |t| 4 | t.string :name 5 | t.string :app_host 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /portal/db/migrate/20160310144552_create_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateJobs < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :jobs do |t| 4 | t.string :status 5 | t.references :team 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /portal/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /portal/app/models/job.rb: -------------------------------------------------------------------------------- 1 | class Job < ActiveRecord::Base 2 | belongs_to :team 3 | 4 | def running? 5 | status == 'Running' 6 | end 7 | 8 | def waiting? 9 | status == 'Waiting' 10 | end 11 | 12 | def enqueued? 13 | running? || waiting? 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /provisioning/bench/ansible/playbooks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_playbook: 00_base.yml 3 | - import_playbook: 01_user.yml 4 | - import_playbook: 02_golang.yml 5 | - import_playbook: 03_bench.yml 6 | - import_playbook: 04_userdata.yml 7 | - import_playbook: 05_build.yml 8 | - import_playbook: 06_kernel.yml 9 | -------------------------------------------------------------------------------- /webapp/php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "slim/slim": "^4.7", 4 | "slim/php-view": "^3.1", 5 | "slim/flash": "^0.4.0", 6 | "slim/psr7": "^1.4", 7 | "php-di/php-di": "^7.0", 8 | "friendsofphp/proxy-manager-lts": "^1.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /provisioning/image/files/etc/nginx/sites-available/isucon.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | client_max_body_size 10m; 5 | root /home/isucon/private_isu/webapp/public/; 6 | 7 | location / { 8 | proxy_set_header Host $host; 9 | proxy_pass http://localhost:8080; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /provisioning/image/ansible/09_build_benchmarker.yml: -------------------------------------------------------------------------------- 1 | - hosts: guests:extras 2 | become: yes 3 | become_user: isucon 4 | gather_facts: no 5 | tasks: 6 | - name: build benchmarker 7 | shell: bash -lc 'cd /home/isucon/private_isu/benchmarker ; make' 8 | when: allinone is defined and allinone 9 | -------------------------------------------------------------------------------- /webapp/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "outDir": "dist", 7 | "rootDir": "src", 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "skipLibCheck": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ansible_old/setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: portal 3 | become: yes 4 | become_user: root 5 | become_method: sudo 6 | roles: 7 | - isucon-base 8 | - portal 9 | - hosts: bench 10 | become: yes 11 | become_user: root 12 | become_method: sudo 13 | roles: 14 | - isucon-base 15 | - bench 16 | -------------------------------------------------------------------------------- /provisioning/image/files/etc/profile.d/bashrc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -f /etc/bashrc ]; then 3 | . /etc/bashrc 4 | fi 5 | 6 | export PATH=/usr/local/bin:$PATH 7 | export PATH=/home/isucon/.local/ruby/bin:$PATH 8 | export PATH=/home/isucon/.local/node/bin:$PATH 9 | export PATH=/home/isucon/.local/go/bin:$PATH 10 | -------------------------------------------------------------------------------- /portal/db/migrate/20160310141714_create_terms.rb: -------------------------------------------------------------------------------- 1 | class CreateTerms < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :terms do |t| 4 | t.string :name, null: false 5 | t.datetime :start_at 6 | t.datetime :end_at 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /portal/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../../config/application', __FILE__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /webapp/node/views/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iscogram 6 | 7 | 8 | 9 |
10 | <%- include('page_header.ejs') %> 11 | -------------------------------------------------------------------------------- /portal/db/migrate/20160319030401_create_scores.rb: -------------------------------------------------------------------------------- 1 | class CreateScores < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :scores do |t| 4 | t.boolean :pass 5 | t.integer :score 6 | t.text :message 7 | t.references :team 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /provisioning/bench/ansible/00_base.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: bench 3 | become: yes 4 | gather_facts: no 5 | tasks: 6 | - apt: autoclean=yes 7 | - apt: update_cache=yes 8 | - apt: upgrade=dist 9 | - apt: name=build-essential state=present 10 | - apt: name=unzip state=present 11 | - apt: name=acl state=present 12 | -------------------------------------------------------------------------------- /portal/app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /benchmarker/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/catatsuy/private-isu/benchmarker 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.11.0 7 | github.com/marcw/cachecontrol v0.0.0-20140722115028-30341fe9a7d5 8 | ) 9 | 10 | require ( 11 | github.com/andybalholm/cascadia v1.3.3 // indirect 12 | golang.org/x/net v0.47.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /portal/config/initializers/sidekiq.rb: -------------------------------------------------------------------------------- 1 | Sidekiq.configure_server do |config| 2 | config.redis = { url: "redis://#{ENV['REDIS_HOST']}:#{ENV['REDIS_PORT']}/1", password: ENV['REDIS_PASS'] } 3 | end 4 | 5 | Sidekiq.configure_client do |config| 6 | config.redis = { url: "redis://#{ENV['REDIS_HOST']}:#{ENV['REDIS_PORT']}/1", password: ENV['REDIS_PASS'] } 7 | end 8 | -------------------------------------------------------------------------------- /provisioning/bench/ansible/04_userdata.yml: -------------------------------------------------------------------------------- 1 | - hosts: bench 2 | become: yes 3 | become_user: isucon 4 | gather_facts: no 5 | tasks: 6 | - name: create userdata 7 | tags: userdata 8 | ansible.builtin.unarchive: src=https://github.com/catatsuy/private-isu/releases/download/img/img.zip dest=/home/isucon/private_isu.git/benchmarker/userdata remote_src=yes 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | portal/log 3 | portal/tmp 4 | 5 | ### Portal 6 | # Ignore bundler config. 7 | /portal/.bundle 8 | /portal/vendor/bundle 9 | 10 | # Ignore the default SQLite database. 11 | /portal/db/*.sqlite3 12 | /portal/db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /portal/log/* 16 | !/portal/log/.keep 17 | /portal/tmp 18 | .vagrant 19 | -------------------------------------------------------------------------------- /webapp/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "private-isu" 3 | version = "0.1.0" 4 | description = "private-isu webapp implementation for Python" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "flask>=3.1.0", 9 | "flask-session[memcached]>=0.8.0", 10 | "gunicorn>=23.0.0", 11 | "mysqlclient~=2.2.7", 12 | "pymemcache>=4.0.0", 13 | ] 14 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/files/nginx/shanai-isucon-portal: -------------------------------------------------------------------------------- 1 | upstream isucon { 2 | server 127.0.0.1:8080; 3 | } 4 | 5 | server { 6 | listen 80; 7 | server_name _; 8 | 9 | root /var/www/isucon_portal/current/public; 10 | 11 | include /etc/nginx/proxy_params; 12 | 13 | try_files $uri @puma; 14 | location @puma { 15 | proxy_pass http://isucon; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /provisioning/bench/ansible/03_bench.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: bench 3 | become: yes 4 | become_user: isucon 5 | gather_facts: no 6 | tasks: 7 | - name: clean private_isu.git 8 | file: path=/home/isucon/private_isu.git state=absent 9 | - name: clone repos 10 | git: 11 | repo=https://github.com/catatsuy/private-isu.git 12 | dest=/home/isucon/private_isu.git 13 | -------------------------------------------------------------------------------- /provisioning/image/files/home/isucon/env.sh: -------------------------------------------------------------------------------- 1 | PATH=/usr/local/bin:/home/isucon/.local/ruby/bin:/home/isucon/.local/node/bin:/home/isucon/.local/python3/bin:/home/isucon/.local/perl/bin:/home/isucon/.local/php/bin:/home/isucon/.local/php/sbin:/home/isucon/.local/go/bin:/home/isucon/.local/scala/bin:/usr/bin/:/bin/:$PATH 2 | ISUCONP_DB_USER=isuconp 3 | ISUCONP_DB_PASSWORD=isuconp 4 | ISUCONP_DB_NAME=isuconp 5 | -------------------------------------------------------------------------------- /provisioning/image/ansible/06_createdb.yml: -------------------------------------------------------------------------------- 1 | - hosts: guests:extras 2 | become: yes 3 | gather_facts: yes 4 | tasks: 5 | - name: create mysqldump 6 | get_url: url=https://github.com/catatsuy/private-isu/releases/download/img/dump.sql.bz2 dest=/home/isucon/backup/mysqldump.sql.bz2 force=1 7 | - name: create database 8 | shell: bzcat /home/isucon/backup/mysqldump.sql.bz2 | mysql -u root 9 | -------------------------------------------------------------------------------- /webapp/ruby/config.ru: -------------------------------------------------------------------------------- 1 | require_relative './app.rb' 2 | 3 | # cf: https://github.com/rack/rack/issues/1994 4 | class DropContentTypeFromGet 5 | def initialize(app) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env.delete "CONTENT_TYPE" if env.fetch('REQUEST_METHOD') == 'GET' 11 | @app.call(env) 12 | end 13 | end 14 | 15 | use DropContentTypeFromGet 16 | 17 | run Isuconp::App 18 | -------------------------------------------------------------------------------- /portal/config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | # role :db, %w{deploy@example.com} 2 | role :web, %w{shanai-isucon-portal}, user: fetch(:user) 3 | role :app, %w{shanai-isucon-portal}, user: fetch(:user) 4 | role :batch, %w{shanai-isucon-bench-01}, user: fetch(:user) 5 | 6 | # set :ssh_options, { 7 | # keys: %w(/home/rlisowski/.ssh/id_rsa), 8 | # forward_agent: false, 9 | # auth_methods: %w(password) 10 | # } 11 | -------------------------------------------------------------------------------- /ansible_old/roles/isucon-base/tasks/env.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: set environments 3 | lineinfile: dest=/etc/environment line="{{ item }}" 4 | with_items: 5 | - "DB_USER={{ mysql_user }}" 6 | - "DB_PASS={{ mysql_pass }}" 7 | - "DB_HOST={{ mysql_host }}" 8 | - "DB_PORT={{ mysql_port }}" 9 | - "REDIS_HOST={{ redis_host }}" 10 | - "REDIS_PORT={{ redis_port }}" 11 | - "REDIS_PASS={{ redis_pass }}" 12 | -------------------------------------------------------------------------------- /provisioning/image/ansible/playbooks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - import_playbook: 00_base.yml 3 | - import_playbook: 01_user.yml 4 | - import_playbook: 02_mysql.yml 5 | - import_playbook: 03_nginx.yml 6 | - import_playbook: 04_xbuild.yml 7 | - import_playbook: 05_app.yml 8 | - import_playbook: 06_createdb.yml 9 | - import_playbook: 07_application.yml 10 | - import_playbook: 08_userdata.yml 11 | - import_playbook: 09_build_benchmarker.yml 12 | -------------------------------------------------------------------------------- /portal/app/views/layouts/devise.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | Isucon Portal 4 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 5 | <%= stylesheet_link_tag 'devise', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 7 | <%= csrf_meta_tags %> 8 | 9 |
10 | <%= yield %> 11 |
12 | -------------------------------------------------------------------------------- /portal/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /portal/app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password. You can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /webapp/golang/templates/user.html: -------------------------------------------------------------------------------- 1 | {{ define "content" }} 2 |
3 |
のページ
4 |
投稿数 {{ .PostCount }}
5 |
コメント数 {{ .CommentCount }}
6 |
被コメント数 {{ .CommentedCount }}
7 |
8 | 9 | {{ template "posts.html" .Posts }} 10 | {{ end }} 11 | -------------------------------------------------------------------------------- /ansible_old/roles/bench/tasks/go.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/azavea/ansible-golang/blob/develop/tasks/main.yml 3 | - name: Download, extract and install Go 4 | unarchive: src=http://golang.org/dl/go{{ golang_version }}.{{ golang_os }}-{{ golang_arch }}.tar.gz 5 | dest=/usr/local 6 | copy=no 7 | 8 | - name: Symlink Go into /usr/local/bin 9 | file: src=/usr/local/go/bin/{{ item }} dest=/usr/local/bin/{{ item }} state=link 10 | with_items: 11 | - go 12 | -------------------------------------------------------------------------------- /portal/app/controllers/jobs_controller.rb: -------------------------------------------------------------------------------- 1 | class JobsController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def create 5 | if current_user.team.jobs.any?(&:enqueued?) 6 | flash[:alert] = 'Job already enqueued. Please wait and try later.' 7 | else 8 | job = Job.create(team: current_user.team, status: 'Waiting') 9 | BenchmarkerJob.perform_later(job_id: job.id) 10 | end 11 | ensure 12 | redirect_to :root 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /webapp/php/views/user.php: -------------------------------------------------------------------------------- 1 |
2 |
のページ
3 |
投稿数
4 |
コメント数
5 |
被コメント数
6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /webapp/ruby/views/user.erb: -------------------------------------------------------------------------------- 1 |
2 |
のページ
3 |
投稿数 <%= escape_html post_count %>
4 |
コメント数 <%= escape_html comment_count %>
5 |
被コメント数 <%= escape_html commented_count %>
6 |
7 | 8 | <%= erb :posts, locals: { posts: posts } %> 9 | -------------------------------------------------------------------------------- /webapp/ruby/views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iscogram 6 | 7 | 8 | 9 |
10 | <%= erb :header, locals: { me: me } %> 11 | 12 | <%= yield %> 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /webapp/python/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iscogram 6 | 7 | 8 | 9 |
10 | {% include 'header.html' %} 11 | {% block body %}{% endblock %} 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: init 2 | init: webapp/sql/dump.sql.bz2 benchmarker/userdata/img 3 | 4 | webapp/sql/dump.sql.bz2: 5 | cd webapp/sql && \ 6 | curl -L -O https://github.com/catatsuy/private-isu/releases/download/img/dump.sql.bz2 7 | 8 | benchmarker/userdata/img.zip: 9 | cd benchmarker/userdata && \ 10 | curl -L -O https://github.com/catatsuy/private-isu/releases/download/img/img.zip 11 | 12 | benchmarker/userdata/img: benchmarker/userdata/img.zip 13 | cd benchmarker/userdata && \ 14 | unzip -qq -o img.zip 15 | -------------------------------------------------------------------------------- /webapp/php/views/layout.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iscogram 6 | 7 | 8 | 9 |
10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /provisioning/image/ansible/02_mysql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: guests:extras 3 | become: yes 4 | gather_facts: no 5 | tasks: 6 | - apt: name=mysql-server 7 | - apt: name=memcached 8 | - apt: name=python3-pymysql 9 | - name: create mysql user 10 | mysql_user: 11 | login_user: root 12 | name: isuconp 13 | password: isuconp 14 | priv: '*.*:ALL' 15 | check_implicit_admin: yes 16 | state: present 17 | login_unix_socket: /var/run/mysqld/mysqld.sock 18 | -------------------------------------------------------------------------------- /provisioning/image/files/etc/systemd/system/isu-node.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isu-node 3 | After=syslog.target 4 | 5 | [Service] 6 | WorkingDirectory=/home/isucon/private_isu/webapp/node 7 | EnvironmentFile=/home/isucon/env.sh 8 | Environment=NODE_ENV=production 9 | PIDFile=/home/isucon/private_isu/webapp/node/server.pid 10 | 11 | User=isucon 12 | Group=isucon 13 | ExecStart=/home/isucon/.local/node/bin/node dist/app.js 14 | ExecStop=/bin/kill -s QUIT $MAINPID 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /portal/app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.submit "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /webapp/node/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM node:24-slim 3 | 4 | RUN \ 5 | --mount=type=cache,target=/var/lib/apt,sharing=locked \ 6 | --mount=type=cache,target=/var/cache/apt,sharing=locked \ 7 | apt-get update -qq && apt-get install -y build-essential default-libmysqlclient-dev openssl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | WORKDIR /home/webapp 11 | 12 | COPY package.json tsconfig.json ./ 13 | RUN npm install 14 | 15 | COPY . . 16 | RUN npm run build 17 | 18 | CMD [ "node", "dist/app.js" ] 19 | -------------------------------------------------------------------------------- /portal/app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.submit "Send me reset password instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /webapp/node/views/user.ejs: -------------------------------------------------------------------------------- 1 | <%- include('header.ejs') %> 2 |
3 |
のページ
4 |
投稿数 <%= post_count %>
5 |
コメント数 <%= comment_count %>
6 |
被コメント数 <%= commented_count %>
7 |
8 | 9 | <%- include('posts.ejs', {posts: posts, imageUrl: imageUrl}) %> 10 | <%- include('footer.ejs') %> 11 | -------------------------------------------------------------------------------- /provisioning/image/files/etc/systemd/system/isu-go.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isu-go 3 | After=syslog.target 4 | 5 | [Service] 6 | WorkingDirectory=/home/isucon/private_isu/webapp/golang 7 | EnvironmentFile=/home/isucon/env.sh 8 | Environment=RACK_ENV=production 9 | PIDFile=/home/isucon/private_isu/webapp/golang/server.pid 10 | 11 | User=isucon 12 | Group=isucon 13 | ExecStart=/home/isucon/private_isu/webapp/golang/app -bind "127.0.0.1:8080" 14 | ExecStop=/bin/kill -s QUIT $MAINPID 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /provisioning/bench/ansible/01_user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: bench 3 | become: yes 4 | gather_facts: no 5 | tasks: 6 | - name: change timezone to JST 7 | shell: cp -p /usr/share/zoneinfo/Japan /etc/localtime || true 8 | - group: name=wheel 9 | - user: name=isucon groups=wheel shell=/bin/bash 10 | - copy: src=../files/etc/profile.d/bashrc dest=/home/isucon/.profile owner=isucon mode=755 11 | - lineinfile: dest=/etc/sudoers state=present regexp='^%wheel ALL\=' line='%wheel ALL=(ALL) NOPASSWD:ALL' validate='visudo -cf %s' 12 | -------------------------------------------------------------------------------- /webapp/python/templates/user.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |
4 |
のページ
5 |
投稿数 {{ post_count }}
6 |
コメント数 {{ comment_count }}
7 |
被コメント数 {{ commented_count }}
8 |
9 |
10 | {% include 'posts.html' %} 11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /portal/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | Rails.application.config.assets.precompile += %w( devise.css ) 12 | -------------------------------------------------------------------------------- /provisioning/image/files/etc/systemd/system/isu-python.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isu-python 3 | After=syslog.target 4 | 5 | [Service] 6 | WorkingDirectory=/home/isucon/private_isu/webapp/python 7 | EnvironmentFile=/home/isucon/env.sh 8 | PIDFile=/home/isucon/private_isu/webapp/python/server.pid 9 | 10 | User=isucon 11 | Group=isucon 12 | ExecStart=/home/isucon/private_isu/webapp/python/.venv/bin/gunicorn app:app -b 0.0.0.0:8080 --log-file - --access-logfile - 13 | ExecStop=/bin/kill -s QUIT $MAINPID 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /portal/bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) 11 | Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) } 12 | gem 'spring', match[1] 13 | require 'spring/binstub' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /.github/workflows/renovate-config-validator-ci.yml: -------------------------------------------------------------------------------- 1 | name: Renovate Config Validator 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | paths: 7 | - "renovate.json" 8 | - ".github/workflows/renovate-config-validator-ci.yml" 9 | 10 | jobs: 11 | renovate-config-validator: 12 | timeout-minutes: 10 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v6 18 | 19 | - name: validate renovate.json 20 | run: npx --package=renovate@latest -c renovate-config-validator 21 | -------------------------------------------------------------------------------- /portal/app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def index 5 | my_team = current_user.team 6 | 7 | if my_team 8 | @my_scores = Score.where(team: my_team).order(:created_at).reverse_order 9 | end 10 | 11 | # Fetch stats current - 60min (5 * 12) 12 | @chart_data = Score.history(time: Time.new) 13 | @ordered_score = Score.ordered_stats(time: Time.new, limit: 10) 14 | 15 | @jobs = Job.where(status: ['Running', 'waiting']).order(:id) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /ansible_old/roles/isucon-base/tasks/user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create isucon user 3 | user: name=isucon groups=www-data createhome=yes shell=/bin/bash 4 | 5 | - name: allow isucon user to use sudo 6 | lineinfile: dest=/etc/sudoers.d/isucon create=yes line="isucon ALL=(ALL) NOPASSWD:ALL" 7 | 8 | - name: set authorized_keys 9 | authorized_key: user=isucon key="https://github.com/{{ item }}.keys" 10 | with_items: "{{ key_github_users }}" 11 | 12 | - name: create directory for portal app 13 | file: path="/var/www" state="directory" owner=root group=www-data mode=0775 14 | -------------------------------------------------------------------------------- /webapp/ruby/Dockerfile: -------------------------------------------------------------------------------- 1 | #syntax=docker/dockerfile:1 2 | FROM ruby:3.4-slim 3 | 4 | RUN \ 5 | --mount=type=cache,target=/var/lib/apt,sharing=locked \ 6 | --mount=type=cache,target=/var/cache/apt,sharing=locked \ 7 | apt-get update -qq \ 8 | && apt-get install -y build-essential default-libmysqlclient-dev \ 9 | && rm -rf /var/lib/apt/lists/* 10 | 11 | WORKDIR /home/webapp 12 | COPY Gemfile Gemfile.lock ./ 13 | RUN bundle config set --local path 'vendor/bundle' && bundle install 14 | COPY . . 15 | 16 | ENTRYPOINT ["bundle", "exec", "unicorn", "-c", "unicorn_config.rb"] 17 | -------------------------------------------------------------------------------- /provisioning/image/files/etc/systemd/system/isu-ruby.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=isu-ruby 3 | After=syslog.target 4 | 5 | [Service] 6 | WorkingDirectory=/home/isucon/private_isu/webapp/ruby 7 | EnvironmentFile=/home/isucon/env.sh 8 | Environment=RACK_ENV=production 9 | PIDFile=/home/isucon/private_isu/webapp/ruby/unicorn.pid 10 | 11 | User=isucon 12 | Group=isucon 13 | ExecStart=/home/isucon/.local/ruby/bin/bundle exec unicorn -c unicorn_config.rb 14 | ExecStop=/bin/kill -s QUIT $MAINPID 15 | ExecReload=/bin/kill -s USR2 $MAINPID 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /webapp/golang/templates/banned.html: -------------------------------------------------------------------------------- 1 | {{ define "content" }} 2 |
3 |
4 | {{ range .Users }} 5 |
6 | 7 |
8 | {{ end }} 9 |
10 | 11 | 12 |
13 |
14 |
15 | {{ end }} 16 | -------------------------------------------------------------------------------- /webapp/python/templates/header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Iscogram

4 |
5 |
6 | {% if not me %} 7 |
ログイン
8 | {% else %} 9 |
さん
10 | {% if me['authority'] == 1 %} 11 |
管理者用ページ
12 | {% endif %} 13 |
ログアウト
14 | {% endif %} 15 |
16 |
17 | -------------------------------------------------------------------------------- /provisioning/image/ansible/08_userdata.yml: -------------------------------------------------------------------------------- 1 | - hosts: guests:extras 2 | become: yes 3 | gather_facts: no 4 | tasks: 5 | - apt: name=unzip state=present 6 | when: allinone is defined and allinone 7 | 8 | - hosts: guests:extras 9 | become: yes 10 | become_user: isucon 11 | gather_facts: no 12 | tasks: 13 | - name: create userdata 14 | tags: userdata 15 | ansible.builtin.unarchive: src=https://github.com/catatsuy/private-isu/releases/download/img/img.zip dest=/home/isucon/private_isu/benchmarker/userdata remote_src=yes 16 | when: allinone is defined and allinone 17 | -------------------------------------------------------------------------------- /webapp/node/views/page_header.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Iscogram

4 |
5 |
6 | <% if (!me) { %> 7 |
ログイン
8 | <% } else { %> 9 |
さん
10 | <% if (me.authority === 1) { %> 11 |
管理者用ページ
12 | <% } %> 13 |
ログアウト
14 | <% } %> 15 |
16 |
17 | -------------------------------------------------------------------------------- /portal/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /webapp/ruby/views/banned.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <% users.each do |u| %> 4 |
5 | 6 |
7 | <% end %> 8 |
9 | 10 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /webapp/golang/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/catatsuy/private-isu/webapp/golang 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf 7 | github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304 8 | github.com/go-chi/chi/v5 v5.2.3 9 | github.com/go-sql-driver/mysql v1.9.3 10 | github.com/gorilla/sessions v1.4.0 11 | github.com/jmoiron/sqlx v1.4.0 12 | ) 13 | 14 | require ( 15 | filippo.io/edwards25519 v1.1.0 // indirect 16 | github.com/gorilla/securecookie v1.1.2 // indirect 17 | github.com/memcachier/mc/v3 v3.0.3 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /webapp/ruby/views/header.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Iscogram

4 |
5 |
6 | <% if me.nil? %> 7 |
ログイン
8 | <% else %> 9 |
さん
10 | <% if me[:authority] == 1 %> 11 |
管理者用ページ
12 | <% end %> 13 |
ログアウト
14 | <% end %> 15 |
16 |
17 | -------------------------------------------------------------------------------- /portal/app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> 9 |
10 | 11 |
12 | <%= f.submit "Resend confirmation instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /webapp/etc/nginx/conf.d/php.conf.org: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | client_max_body_size 10m; 5 | root /public; 6 | 7 | location / { 8 | try_files $uri /index.php$is_args$args; 9 | } 10 | 11 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 12 | location ~ \.php { 13 | #fastcgi_split_path_info ^(.+\.php)(/.+)$; 14 | include fastcgi_params; 15 | fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name; 16 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 17 | fastcgi_index index.php; 18 | fastcgi_pass app:9000; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /webapp/python/templates/banned.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |
4 |
5 | {% for u in users %} 6 |
7 | 8 |
9 | {% endfor %} 10 |
11 | 12 | 13 |
14 |
15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /provisioning/bench/ansible/06_kernel.yml: -------------------------------------------------------------------------------- 1 | - hosts: bench 2 | become: yes 3 | gather_facts: no 4 | tasks: 5 | - name: Increase file limits for sysctl 6 | copy: 7 | dest: /etc/sysctl.d/50-fs.conf 8 | content: | 9 | fs.file-max=655360 10 | 11 | - name: Tune up network 12 | copy: 13 | dest: /etc/sysctl.d/92-net.conf 14 | content: | 15 | net.core.somaxconn=65535 16 | 17 | - name: Increase file limits 18 | copy: 19 | dest: /etc/security/limits.d/50-nofile.conf 20 | content: | 21 | isucon hard nofile 10000 22 | isucon soft nofile 10000 23 | -------------------------------------------------------------------------------- /webapp/php/views/banned.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 | 8 |
9 | 10 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/tasks/redis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install redis 3 | apt: name=redis-server state=latest update_cache=yes 4 | 5 | - name: start redis 6 | service: name=redis-server state=running enabled=yes 7 | 8 | - name: require password 9 | lineinfile: dest=/etc/redis/redis.conf line="requirepass {{ redis_pass }}" state=present 10 | notify: reload redis 11 | 12 | - name: unbind 127.0.0.1 13 | lineinfile: dest=/etc/redis/redis.conf line="bind 127.0.0.1" state=absent 14 | notify: restart redis 15 | 16 | - name: bind 0.0.0.0 17 | lineinfile: dest=/etc/redis/redis.conf line="bind 0.0.0.0" state=present 18 | notify: restart redis 19 | -------------------------------------------------------------------------------- /webapp/php/views/header.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Iscogram

4 |
5 |
6 | 7 |
ログイン
8 | 9 |
さん
10 | 11 |
管理者用ページ
12 | 13 |
ログアウト
14 | 15 |
16 |
17 | -------------------------------------------------------------------------------- /webapp/node/views/banned.ejs: -------------------------------------------------------------------------------- 1 | <%- include('header.ejs') %> 2 |
3 |
4 | <% users.forEach(function(user) { %> 5 |
6 | 7 |
8 | <% }); %> 9 |
10 | 11 | 12 |
13 |
14 |
15 | <%- include('footer.ejs') %> 16 | -------------------------------------------------------------------------------- /provisioning/bench/ansible/02_golang.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: bench 3 | become: yes 4 | gather_facts: no 5 | tasks: 6 | - name: mkdir /usr/local/go 7 | file: path=/usr/local/go state=directory 8 | - name: check uname 9 | shell: uname -s | tr [A-Z] [a-z] 10 | register: uname_res 11 | check_mode: no 12 | - name: check arch 13 | command: dpkg --print-architecture 14 | register: arch_res 15 | check_mode: no 16 | - name: download go binary 17 | unarchive: 18 | src="https://golang.org/dl/go1.25.5.{{ uname_res.stdout }}-{{ arch_res.stdout }}.tar.gz" 19 | dest=/usr/local/ 20 | remote_src=yes 21 | -------------------------------------------------------------------------------- /provisioning/image/files/etc/nginx/sites-available/isucon-php.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | client_max_body_size 10m; 5 | root /home/isucon/private_isu/webapp/public; 6 | 7 | location / { 8 | try_files $uri /index.php$is_args$args; 9 | } 10 | 11 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 12 | location ~ \.php { 13 | include fastcgi_params; 14 | fastcgi_param SCRIPT_FILENAME /home/isucon/private_isu/webapp/php/$fastcgi_script_name; 15 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 16 | fastcgi_index index.php; 17 | fastcgi_pass 127.0.0.1:9000; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /portal/README.md: -------------------------------------------------------------------------------- 1 | # isucon portal 2 | 3 | ### require 4 | 5 | * Redis 6 | * Sidekiq 7 | 8 | ``` 9 | apt install -y libmysqlclient-dev 10 | apt install -y sqlite3 11 | apt install -y redis 12 | ``` 13 | 14 | ## Development 15 | 16 | ### Portal webapp 17 | 18 | ``` 19 | bundle install -j4 --path=vendor/bundle 20 | bundle exec rake db:setup db:seed 21 | ``` 22 | 23 | ``` 24 | bundle exec rails s -b 0.0.0.0 -p 3000 25 | ``` 26 | 27 | And open `http://localhost:3000` 28 | 29 | ## Run in production 30 | 31 | ### asset:precompile 32 | 33 | ``` 34 | bundle exec rails assets:precompile 35 | ``` 36 | 37 | ### Sidekiq 38 | 39 | ``` 40 | bundle exec sidekiq 41 | ``` 42 | 43 | FIXME: 44 | -------------------------------------------------------------------------------- /portal/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | 9 | Team.create([ 10 | { name: 'あんこうチーム', users: [User.create(name: 'foo', password: 'hogehoge', email: 'foo@foobar.local')], app_host: 'localhost:8080' }, 11 | { name: 'カメさんチーム', users: [User.create(name: 'bar', password: 'hogehoge', email: 'bar@foobar.local')], app_host: 'localhost:8080' } 12 | ]) 13 | 14 | -------------------------------------------------------------------------------- /webapp/python/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.14-slim 2 | 3 | RUN \ 4 | --mount=type=cache,target=/var/lib/apt,sharing=locked \ 5 | --mount=type=cache,target=/var/cache/apt,sharing=locked \ 6 | apt-get update -qq && apt-get install -y build-essential default-libmysqlclient-dev pkg-config 7 | 8 | RUN python -m pip install --upgrade pip && python -m pip install uv 9 | 10 | RUN mkdir -p /home/webapp 11 | WORKDIR /home/webapp 12 | 13 | COPY pyproject.toml uv.lock ./ 14 | RUN --mount=type=cache,target=/root/.cache/uv \ 15 | uv sync --compile-bytecode 16 | COPY . . 17 | 18 | ENTRYPOINT [ "/home/webapp/.venv/bin/gunicorn", "app:app", "-b", "0.0.0.0:8080", "--log-file", "-", "--access-logfile", "-" ] 19 | -------------------------------------------------------------------------------- /portal/Gemfile.org: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '~>7.0' 4 | gem 'sass-rails' 5 | gem 'uglifier' 6 | gem 'coffee-rails' 7 | 8 | gem 'sidekiq' 9 | 10 | gem 'jquery-rails' 11 | gem 'turbolinks' 12 | gem 'jbuilder' 13 | gem 'chartkick' 14 | gem 'bootstrap-sass' 15 | gem 'devise' 16 | gem 'bcrypt' 17 | # webserver 18 | gem 'puma' 19 | 20 | gem 'mini_racer' 21 | 22 | group :production do 23 | gem 'mysql2' 24 | end 25 | 26 | group :development, :test do 27 | gem 'sqlite3' 28 | gem 'byebug' 29 | end 30 | 31 | group :development do 32 | gem 'web-console' 33 | gem 'spring' 34 | gem 'capistrano-sidekiq' 35 | gem 'capistrano-rails' 36 | gem 'capistrano3-puma' 37 | end 38 | 39 | -------------------------------------------------------------------------------- /provisioning/image/ansible/01_user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: guests:extras 3 | gather_facts: no 4 | become: yes 5 | tasks: 6 | - name: change timezone to JST 7 | shell: cp -p /usr/share/zoneinfo/Japan /etc/localtime || true 8 | - group: name=wheel 9 | - user: name=isucon groups=wheel shell=/bin/bash 10 | - name: change the permission for home directory 11 | file: 12 | path: /home/isucon 13 | state: directory 14 | mode: '0755' 15 | - copy: src=../files/etc/profile.d/bashrc dest=/home/isucon/.profile owner=isucon mode=755 16 | - lineinfile: dest=/etc/sudoers state=present regexp='^%wheel ALL\=' line='%wheel ALL=(ALL) NOPASSWD:ALL' validate='visudo -cf %s' 17 | -------------------------------------------------------------------------------- /webapp/php/views/register.php: -------------------------------------------------------------------------------- 1 |
2 |

ユーザー登録

3 |
4 | 5 | 6 |
7 | 8 |
9 | 10 | 11 |
12 |
13 | 17 |
18 | パスワード 19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /webapp/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "private-isu-node", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "build": "tsc", 7 | "dev": "ts-node-dev --respawn src/app.ts", 8 | "start": "node dist/app.js" 9 | }, 10 | "dependencies": { 11 | "memcached": "^2.2.2", 12 | "ejs": "^3.1.10", 13 | "multer": "^2.0.2", 14 | "mysql2": "^3.15.3", 15 | "hono": "^4.11.1", 16 | "@hono/node-server": "^1.19.7" 17 | }, 18 | "devDependencies": { 19 | "@types/ejs": "^3.1.5", 20 | "@types/memcached": "^2.2.10", 21 | "@types/multer": "^2.0.0", 22 | "ts-node-dev": "^2.0.0", 23 | "typescript": "^5.9.3", 24 | "@types/node": "^24.10.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webapp/ruby/views/register.erb: -------------------------------------------------------------------------------- 1 |
2 |

ユーザー登録

3 |
4 | 5 | <% if flash[:notice] %> 6 |
7 | <%= escape_html(flash[:notice]) %> 8 |
9 | <% end %> 10 | 11 |
12 |
13 | 17 |
18 | パスワード 19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /webapp/golang/templates/register.html: -------------------------------------------------------------------------------- 1 | {{ define "content" }} 2 |
3 |

ユーザー登録

4 |
5 | 6 | {{if .Flash}} 7 |
8 | {{.Flash}} 9 |
10 | {{end}} 11 | 12 |
13 |
14 | 18 |
19 | パスワード 20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 | {{ end }} 28 | -------------------------------------------------------------------------------- /portal/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /portal/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /portal/app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |

Log in

2 | 3 | <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 4 |
5 | <%= f.label :email %>
6 | <%= f.email_field :email, autofocus: true %> 7 |
8 | 9 |
10 | <%= f.label :password %>
11 | <%= f.password_field :password, autocomplete: "off" %> 12 |
13 | 14 | <% if devise_mapping.rememberable? -%> 15 |
16 | <%= f.check_box :remember_me %> 17 | <%= f.label :remember_me %> 18 |
19 | <% end -%> 20 | 21 |
22 | <%= f.submit "Log in" %> 23 |
24 | <% end %> 25 | 26 | <%= render "devise/shared/links" %> 27 | -------------------------------------------------------------------------------- /webapp/node/views/register.ejs: -------------------------------------------------------------------------------- 1 | <%- include('header.ejs') %> 2 |
3 |

ユーザー登録

4 |
5 | 6 | <% if (messages.notice) { %> 7 |
8 | <%= messages.notice %> 9 |
10 | <% } %> 11 | 12 |
13 |
14 | 18 |
19 | パスワード 20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 | <%- include('footer.ejs') %> 28 | -------------------------------------------------------------------------------- /webapp/php/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM php:8.3-fpm-bookworm 3 | 4 | RUN \ 5 | --mount=type=cache,target=/var/lib/apt,sharing=locked \ 6 | --mount=type=cache,target=/var/cache/apt,sharing=locked \ 7 | apt-get update -qq && apt-get install -y unzip libmemcached-dev zlib1g-dev libssl-dev 8 | 9 | RUN docker-php-ext-install pdo pdo_mysql 10 | 11 | RUN pecl install memcached \ 12 | && docker-php-ext-enable memcached 13 | 14 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 15 | 16 | RUN curl -sS https://getcomposer.org/installer | php \ 17 | && mv composer.phar /usr/local/bin/composer 18 | 19 | COPY ./composer.json /var/www/html 20 | COPY ./composer.lock /var/www/html 21 | WORKDIR /var/www/html 22 | 23 | RUN composer install --no-dev 24 | 25 | COPY . /var/www/html 26 | -------------------------------------------------------------------------------- /webapp/php/views/login.php: -------------------------------------------------------------------------------- 1 |
2 |

ログイン

3 |
4 | 5 | 6 |
7 | 8 |
9 | 10 | 11 |
12 |
13 | 17 |
18 | パスワード 19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 | 27 |
28 | ユーザー登録 29 |
30 | -------------------------------------------------------------------------------- /webapp/ruby/views/login.erb: -------------------------------------------------------------------------------- 1 |
2 |

ログイン

3 |
4 | 5 | <% if flash[:notice] %> 6 |
7 | <%= escape_html(flash[:notice]) %> 8 |
9 | <% end %> 10 | 11 |
12 |
13 | 17 |
18 | パスワード 19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 | 27 |
28 | ユーザー登録 29 |
30 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/tasks/nginx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install nginx 3 | apt: name=nginx state=latest update_cache=yes 4 | 5 | - name: start nginx 6 | service: name=nginx state=running enabled=yes 7 | notify: restart nginx 8 | 9 | - name: delete default virtualhost setting 10 | file: path=/etc/nginx/sites-enabled/default state=absent 11 | notify: reload nginx 12 | 13 | - name: copy nginx config for shanai-isucon-portal 14 | copy: src=nginx/shanai-isucon-portal dest=/etc/nginx/sites-available/shanai-isucon-portal owner=root group=root mode="u=rw,g=r,o=r" 15 | notify: reload nginx 16 | 17 | - name: enable shanai-isucon-portal virtualhost 18 | file: src=/etc/nginx/sites-available/shanai-isucon-portal dest=/etc/nginx/sites-enabled/shanai-isucon-portal state=link 19 | notify: reload nginx 20 | -------------------------------------------------------------------------------- /webapp/golang/templates/login.html: -------------------------------------------------------------------------------- 1 | {{ define "content" }} 2 |
3 |

ログイン

4 |
5 | 6 | {{if .Flash}} 7 |
8 | {{.Flash}} 9 |
10 | {{end}} 11 | 12 |
13 |
14 | 18 |
19 | パスワード 20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 | ユーザー登録 30 |
31 | {{ end }} 32 | -------------------------------------------------------------------------------- /webapp/python/templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |
4 |

ユーザー登録

5 |
6 | 7 | {% set notice = get_flashed_messages() %} 8 | {% if notice %} 9 |
10 | {{ '\n'.join(notice) }} 11 |
12 | {% endif %} 13 | 14 |
15 |
16 | 20 |
21 | パスワード 22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /webapp/node/views/login.ejs: -------------------------------------------------------------------------------- 1 | <%- include('header.ejs') %> 2 |
3 |

ログイン

4 |
5 | 6 | <% if (messages.notice) { %> 7 |
<%= messages.notice %>
8 | <% } %> 9 | 10 |
11 |
12 | 16 |
17 | パスワード 18 | 19 |
20 |
21 | 22 |
23 |
24 |
25 | 26 |
27 | ユーザー登録 28 |
29 | <%- include('footer.ejs') %> 30 | -------------------------------------------------------------------------------- /benchmarker/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "io" 7 | mrand "math/rand/v2" 8 | ) 9 | 10 | func GetMD5(data []byte) string { 11 | return fmt.Sprintf("%x", md5.Sum(data)) 12 | } 13 | 14 | func GetMD5ByIO(r io.Reader) string { 15 | bytes, err := io.ReadAll(r) 16 | if err != nil { 17 | fmt.Println(err) 18 | } 19 | return GetMD5(bytes) 20 | } 21 | 22 | var ( 23 | lunRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 24 | ) 25 | 26 | func RandomNumber(max int) int { 27 | return mrand.IntN(max) 28 | } 29 | 30 | func RandomLUNStr(n int) string { 31 | return randomStr(n, lunRunes) 32 | } 33 | 34 | func randomStr(n int, s []rune) string { 35 | buf := make([]byte, n) 36 | for i := range n { 37 | buf[i] = byte(s[mrand.IntN(len(s))]) 38 | } 39 | return string(buf) 40 | } 41 | -------------------------------------------------------------------------------- /portal/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: mysql2 9 | encoding: utf8mb4 10 | username: <%= ENV['DB_USER'] %> 11 | password: <%= ENV['DB_PASS'] %> 12 | host: <%= ENV['DB_HOST'] %> 13 | port: <%= ENV['DB_PORT'] %> 14 | pool: 5 15 | timeout: 5000 16 | 17 | development: 18 | <<: *default 19 | database: isucon_portal_development 20 | 21 | # Warning: The database defined as "test" will be erased and 22 | # re-generated from your development database when you run "rake". 23 | # Do not set this db to the same as development or production. 24 | test: 25 | <<: *default 26 | database: isucon_portal_test 27 | 28 | production: 29 | <<: *default 30 | database: isucon_portal_production 31 | 32 | -------------------------------------------------------------------------------- /portal/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | Isucon Portal 3 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 4 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 5 | <%= csrf_meta_tags %> 6 | 7 |
8 |
9 |
10 | Login: <%= current_user ? (current_user.name + ' (' + current_user.team.name + ')') : 'not login' %> 11 |
12 |
13 | <%= button_to 'Logout', destroy_user_session_path, method: :delete, class: 'btn btn-warning' %> 14 |
15 |
16 |
17 | <% if notice %>

<%= notice %>

<% end %> 18 | <% if alert %>

<%= alert %>

<% end %> 19 |
20 | <%= yield %> 21 |
22 | -------------------------------------------------------------------------------- /webapp/golang/templates/index.html: -------------------------------------------------------------------------------- 1 | {{ define "content" }} 2 |
3 |
4 |
5 | 6 |
7 |
8 | 9 |
10 |
11 | 12 | 13 |
14 | {{if .Flash}} 15 |
16 | {{.Flash}} 17 |
18 | {{end}} 19 |
20 |
21 | 22 | {{ template "posts.html" .Posts }} 23 | 24 |
25 | 26 | 27 |
28 | {{ end }} 29 | -------------------------------------------------------------------------------- /webapp/python/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |
4 |

ログイン

5 |
6 | 7 | {% set messages = get_flashed_messages() %} 8 | {% if messages %} 9 |
10 | {{ '\n'.join(messages) }} 11 |
12 | {% endif %} 13 | 14 |
15 |
16 | 20 |
21 | パスワード 22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 | 30 |
31 | ユーザー登録 32 |
33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /portal/app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign up

2 | 3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 |
12 | <%= f.label :password %> 13 | <% if @minimum_password_length %> 14 | (<%= @minimum_password_length %> characters minimum) 15 | <% end %>
16 | <%= f.password_field :password, autocomplete: "off" %> 17 |
18 | 19 |
20 | <%= f.label :password_confirmation %>
21 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 22 |
23 | 24 |
25 | <%= f.submit "Sign up" %> 26 |
27 | <% end %> 28 | 29 | <%= render "devise/shared/links" %> 30 | -------------------------------------------------------------------------------- /webapp/php/views/index.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 |
9 |
10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 | 25 | 26 |
27 | -------------------------------------------------------------------------------- /webapp/ruby/views/index.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 |
9 |
10 | 11 | 12 |
13 | <% if flash[:notice] %> 14 |
15 | <%= escape_html(flash[:notice]) %> 16 |
17 | <% end %> 18 |
19 |
20 | 21 | <%= erb :posts, locals: { posts: posts } %> 22 | 23 |
24 | 25 | 26 |
27 | -------------------------------------------------------------------------------- /portal/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/tasks/mysql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install mysql 3 | apt: name=mysql-server state=latest default_release=jessie update_cache=yes 4 | 5 | - name: start mysql 6 | service: name=mysql state=running enabled=yes 7 | 8 | - name: copy my.cnf 9 | copy: src=mysql/my.cnf dest=/etc/mysql/my.cnf 10 | notify: reload mysql 11 | 12 | - name: setup mysql db 13 | shell: mysql -uroot --default-character-set=utf8mb4 -e 'CREATE DATABASE IF NOT EXISTS {{ mysql_db }}' 14 | 15 | - name: setup mysql user 16 | shell: | 17 | mysql -uroot -e "GRANT ALL PRIVILEGES ON {{ mysql_db }}.* TO '{{ mysql_user }}'@'localhost' IDENTIFIED BY '{{ mysql_pass }}'" 18 | mysql -uroot -e "GRANT ALL PRIVILEGES ON {{ mysql_db }}.* TO '{{ mysql_user }}'@'%' IDENTIFIED BY '{{ mysql_pass }}'" 19 | mysql -uroot -e "FLUSH PRIVILEGES" 20 | 21 | - name: copy my.cnf 22 | copy: src=mysql/my.cnf dest=/etc/mysql/my.cnf 23 | notify: restart mysql 24 | -------------------------------------------------------------------------------- /portal/app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | <%= f.hidden_field :reset_password_token %> 6 | 7 |
8 | <%= f.label :password, "New password" %>
9 | <% if @minimum_password_length %> 10 | (<%= @minimum_password_length %> characters minimum)
11 | <% end %> 12 | <%= f.password_field :password, autofocus: true, autocomplete: "off" %> 13 |
14 | 15 |
16 | <%= f.label :password_confirmation, "Confirm new password" %>
17 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 18 |
19 | 20 |
21 | <%= f.submit "Change my password" %> 22 |
23 | <% end %> 24 | 25 | <%= render "devise/shared/links" %> 26 | -------------------------------------------------------------------------------- /webapp/node/views/index.ejs: -------------------------------------------------------------------------------- 1 | <%- include('header.ejs') %> 2 |
3 |
4 |
5 | 6 |
7 |
8 | 9 |
10 |
11 | 12 | 13 |
14 | <% if (messages.notice) { %> 15 |
<%= messages.notice %>
16 | <% } %> 17 |
18 |
19 | 20 | <%- include('posts.ejs', { posts: posts }) %> 21 | 22 |
23 | 24 | 25 |
26 | <%- include('footer.ejs') %> 27 | -------------------------------------------------------------------------------- /benchmarker/cli_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestRun_versionFlag(t *testing.T) { 11 | outStream, errStream := new(bytes.Buffer), new(bytes.Buffer) 12 | cli := &CLI{outStream: outStream, errStream: errStream} 13 | args := strings.Split("./benchmarker -version", " ") 14 | 15 | status := cli.Run(args) 16 | if status != ExitCodeOK { 17 | t.Errorf("expected %d to eq %d", status, ExitCodeOK) 18 | } 19 | 20 | expected := fmt.Sprintf("benchmarker version %s", Version) 21 | if !strings.Contains(errStream.String(), expected) { 22 | t.Errorf("expected %q to eq %q", errStream.String(), expected) 23 | } 24 | } 25 | 26 | func TestRun_targetFlag(t *testing.T) { 27 | outStream, errStream := new(bytes.Buffer), new(bytes.Buffer) 28 | cli := &CLI{outStream: outStream, errStream: errStream} 29 | args := strings.Split("./benchmarker -target", " ") 30 | 31 | status := cli.Run(args) 32 | _ = status 33 | } 34 | -------------------------------------------------------------------------------- /benchmarker/userdata/README.md: -------------------------------------------------------------------------------- 1 | # 初期データ 2 | 3 | http://twilog.org/catatsuy をスクレイピングしてラーメン画像を取得してきて、ImageMagickでフィルターをかけて1万枚ぐらいの画像を用意する 4 | 5 | 画像データと生成したmysqldumpファイルはGitHubのreleaseにアップしてある https://github.com/catatsuy/private-isu/releases/tag/img 6 | 7 | ## フィルター 8 | 9 | ```bash 10 | convert $file -blur 20 blur-$file 11 | convert $file -median 10 median-$file 12 | convert $file -sepia-tone '70%' sepia2-$file 13 | convert $file -sepia-tone '90%' sepia-$file 14 | convert $file -modulate 100,90,90 red-$file 15 | convert $file -emboss 10 emboss-$file 16 | convert $file -modulate 100,90,90 red-$file 17 | convert $file -type GrayScale gray-$file 18 | ./toycamera -i 50 -o 150 -s 100 -O 3 -I 3 $file toycamera-$file # http://fmwconcepts.com/imagemagick/toycamera/index.php 19 | convert $file -flop flop-$file 20 | ``` 21 | 22 | ## names.txt 23 | 24 | http://names.mongabay.com/female_names.htm から女性名トップ1000をスクレイピングした 25 | 26 | ## kaomoji.txt 27 | 28 | http://kamoji.wiki.fc2.com/ から「挨拶」をスクレイピングした 29 | -------------------------------------------------------------------------------- /webapp/python/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |
4 |
5 |
6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | 14 |
15 | {% set notice = '\n'.join(get_flashed_messages()) %} 16 | {% if notice %} 17 |
18 | {{ notice }} 19 |
20 | {% endif %} 21 |
22 |
23 | 24 | {% include 'posts.html' %} 25 | 26 |
27 | 28 | 29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /provisioning/README.md: -------------------------------------------------------------------------------- 1 | # 競技用インスタンスのセットアップ方法 2 | 3 | ## webapp 4 | 5 | image/ansible以下に入っているplaybookを順番に実行。 6 | 7 | ``` 8 | $ ansible-playbook -i hosts image/ansible/playbooks.yml 9 | ``` 10 | 11 | ## bench 12 | 13 | bench/ansible以下に入っているplaybookを順番に実行。 14 | 15 | ``` 16 | $ ansible-playbook -i hosts bench/ansible/playbooks.yml 17 | ``` 18 | 19 | ## benchmakerを同梱したwebapp 20 | 21 | webappとbenchmaker両方含むall in oneなインスタンスをセットアップする場合 22 | 23 | ``` 24 | $ ansible-playbook -i hosts image/ansible/playbooks.yml -e 'allinone=True' 25 | ``` 26 | 27 | 同梱したbenchmarkerを動作させるには以下のようにします。 28 | 29 | ```sh 30 | $ sudo su - isucon 31 | $ /home/isucon/private_isu/benchmarker/bin/benchmarker -u /home/isucon/private_isu/benchmarker/userdata -t http:// 32 | ``` 33 | 34 | ## ssh config の例 35 | 36 | ``` 37 | Host isu-app 38 | IdentityFile ~/.ssh/xxx.pem 39 | HostName xxx.xxx.xxx.xxx 40 | User ubuntu 41 | 42 | Host isu-bench 43 | IdentityFile ~/.ssh/xxx.pem 44 | HostName xxx.xxx.xxx.xxx 45 | User ubuntu 46 | ``` 47 | -------------------------------------------------------------------------------- /portal/Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and set up stages 2 | require 'capistrano/setup' 3 | 4 | # Include default deployment tasks 5 | require 'capistrano/deploy' 6 | 7 | # Include tasks from other gems included in your Gemfile 8 | # 9 | # For documentation on these, see for example: 10 | # 11 | # https://github.com/capistrano/rvm 12 | # https://github.com/capistrano/rbenv 13 | # https://github.com/capistrano/chruby 14 | # https://github.com/capistrano/bundler 15 | # https://github.com/capistrano/rails 16 | # https://github.com/capistrano/passenger 17 | # 18 | # require 'capistrano/rvm' 19 | # require 'capistrano/rbenv' 20 | # require 'capistrano/chruby' 21 | # require 'capistrano/rails/migrations' 22 | # require 'capistrano/passenger' 23 | 24 | require 'capistrano/bundler' 25 | require 'capistrano/rails/assets' 26 | require 'capistrano/sidekiq' 27 | require 'capistrano/puma' 28 | 29 | # Load custom tasks from `lib/capistrano/tasks` if you have any defined 30 | Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } 31 | -------------------------------------------------------------------------------- /ansible_old/roles/bench/tasks/benchmarker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: git clone private-isu 3 | git: repo=https://github.com/catatsuy/private-isu.git 4 | dest={{ gopath }}/src/github.com/catatsuy/private-isu 5 | version=master 6 | accept_hostkey=yes 7 | register: benchmarker 8 | 9 | - name: compile 10 | shell: "make" 11 | args: 12 | chdir: "{{ gopath }}/src/github.com/catatsuy/private-isu/benchmarker" 13 | when: benchmarker.changed 14 | 15 | - name: install unzip command 16 | apt: name=unzip state=latest 17 | 18 | - name: download images 19 | get_url: url=https://github.com/catatsuy/private-isu/releases/download/img/img.zip dest={{ gopath }}/src/github.com/catatsuy/private-isu/benchmarker/userdata/img.zip checksum=md5:4e66b924aa095030a9d152ab5f2015de 20 | register: imgzip 21 | 22 | - name: unarchive images 23 | unarchive: src={{ gopath }}/src/github.com/catatsuy/private-isu/benchmarker/userdata/img.zip dest={{ gopath }}/src/github.com/catatsuy/private-isu/benchmarker/userdata copy=no 24 | when: imgzip.changed 25 | -------------------------------------------------------------------------------- /provisioning/image/ansible/03_nginx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: guests:extras 3 | become: yes 4 | gather_facts: no 5 | tasks: 6 | - apt: name=nginx 7 | - name: start nginx 8 | service: name=nginx state=started enabled=yes 9 | notify: restart nginx 10 | - name: clean nginx 11 | file: path=/etc/nginx/sites-available/default state=absent 12 | notify: reload nginx 13 | - name: clean nginx 14 | file: path=/etc/nginx/sites-enabled/default state=absent 15 | notify: reload nginx 16 | - copy: src=../files/etc/nginx/sites-available/isucon.conf dest=/etc/nginx/sites-available/isucon.conf owner=root mode=644 17 | notify: reload nginx 18 | - copy: src=../files/etc/nginx/sites-available/isucon-php.conf dest=/etc/nginx/sites-available/isucon-php.conf owner=root mode=644 19 | notify: reload nginx 20 | - file: src=/etc/nginx/sites-available/isucon.conf dest=/etc/nginx/sites-enabled/isucon.conf state=link 21 | notify: reload nginx 22 | handlers: 23 | - name: reload nginx 24 | shell: /usr/sbin/nginx -t && systemctl reload nginx 25 | -------------------------------------------------------------------------------- /webapp/golang/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iscogram 6 | 7 | 8 | 9 |
10 |
11 |
12 |

Iscogram

13 |
14 |
15 | {{ if eq .Me.ID 0}} 16 | 17 | {{ else }} 18 | 19 | {{ if eq .Me.Authority 1 }} 20 | 21 | {{ end }} 22 | 23 | {{ end }} 24 |
25 |
26 | 27 | {{ template "content" . }} 28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /portal/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require bootstrap-sprockets 17 | //= require chartkick 18 | //= require Chart.bundle 19 | //= require_tree . 20 | $(function () { 21 | 'use strict'; 22 | $('.expand-message').on('click', function () { 23 | var $this = $(this); 24 | var msg = $this.attr('data-message'); 25 | $this.removeClass('clickable'); 26 | $this.text(msg); 27 | $this.off(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /benchmarker/score/score.go: -------------------------------------------------------------------------------- 1 | package score 2 | 3 | import "sync" 4 | 5 | type Score struct { 6 | sync.RWMutex 7 | score int64 8 | sucesses int64 9 | fails int64 10 | } 11 | 12 | var instance *Score 13 | var once sync.Once 14 | 15 | func GetInstance() *Score { 16 | once.Do(func() { 17 | instance = &Score{ 18 | score: 0, 19 | sucesses: 0, 20 | fails: 0, 21 | } 22 | }) 23 | 24 | return instance 25 | } 26 | 27 | func (s *Score) GetScore() int64 { 28 | s.RLock() 29 | score := s.score 30 | s.RUnlock() 31 | 32 | if score <= 0 { 33 | return 0 34 | } 35 | return score 36 | } 37 | 38 | func (s *Score) GetSucesses() int64 { 39 | s.RLock() 40 | sucesses := s.sucesses 41 | s.RUnlock() 42 | return sucesses 43 | } 44 | 45 | func (s *Score) GetFails() int64 { 46 | s.RLock() 47 | fails := s.fails 48 | s.RUnlock() 49 | return fails 50 | } 51 | 52 | func (s *Score) SetScore(point int64) { 53 | s.Lock() 54 | s.score += point 55 | s.sucesses += 1 56 | s.Unlock() 57 | } 58 | 59 | func (s *Score) SetFails(point int64) { 60 | s.Lock() 61 | s.score -= point 62 | s.fails += 1 63 | s.Unlock() 64 | } 65 | -------------------------------------------------------------------------------- /benchmarker/sql/schema.sql: -------------------------------------------------------------------------------- 1 | -- benchmarker/userdata/load.rbから読み込まれる 2 | 3 | DROP TABLE IF EXISTS users; 4 | CREATE TABLE users ( 5 | `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, 6 | `account_name` varchar(64) NOT NULL UNIQUE, 7 | `passhash` varchar(128) NOT NULL, -- SHA2 512 non-binary (hex) 8 | `authority` tinyint(1) NOT NULL DEFAULT 0, 9 | `del_flg` tinyint(1) NOT NULL DEFAULT 0, 10 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 11 | ) DEFAULT CHARSET=utf8mb4; 12 | 13 | DROP TABLE IF EXISTS posts; 14 | CREATE TABLE posts ( 15 | `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, 16 | `user_id` int NOT NULL, 17 | `mime` varchar(64) NOT NULL, 18 | `imgdata` mediumblob NOT NULL, 19 | `body` text NOT NULL, 20 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 21 | ) DEFAULT CHARSET=utf8mb4; 22 | 23 | DROP TABLE IF EXISTS comments; 24 | CREATE TABLE comments ( 25 | `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, 26 | `post_id` int NOT NULL, 27 | `user_id` int NOT NULL, 28 | `comment` text NOT NULL, 29 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 30 | ) DEFAULT CHARSET=utf8mb4; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 KANEKO Tatsuya 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 | -------------------------------------------------------------------------------- /portal/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 1e1cddd1697329896984b1bcfe58b639f9c5d630bb2659c4a9e5a8b3084881fcbc90f36f8b33afce69c765c3b7bce45eecbc7db6e5e2e250a0c8afeabc0cfcf2 15 | 16 | test: 17 | secret_key_base: 156264de39dc033868dd60cc668124682cac3c1c221c94ccb5c52ad4a677e8480ddc926cc1422ed130eedfcba4d0720dba32a16d52547a4038df8425da31aa7b 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: 4fd542257858486c18a360cab107db4fc7caff13eecee1a228fe8fe6cb41f4e13743e0592dc96081ad8153def9b312f907b153fc60bfe4d51cb4aa7dca706d99 23 | # secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 24 | -------------------------------------------------------------------------------- /portal/app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name) %>
3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
24 | <% end -%> 25 | <% end -%> 26 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:recommended" 4 | ], 5 | "customManagers": [ 6 | { 7 | "customType": "regex", 8 | "managerFilePatterns": [ 9 | "/provisioning/image/ansible/04_xbuild.yml/" 10 | ], 11 | "matchStrings": [ 12 | "datasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?\n.*?-install v?(?[0-9.]*).*\n" 13 | ] 14 | }, 15 | { 16 | "customType": "regex", 17 | "managerFilePatterns": [ 18 | "/provisioning/bench/ansible/02_golang.yml/" 19 | ], 20 | "datasourceTemplate": "golang-version", 21 | "depNameTemplate": "golang", 22 | "matchStrings": [ 23 | "go(?[0-9]*.[0-9]*.[0-9]*)" 24 | ] 25 | } 26 | ], 27 | "packageRules": [ 28 | { 29 | "matchDatasources": [ 30 | "docker" 31 | ], 32 | "matchPackageNames": [ 33 | "nginx" 34 | ], 35 | "versioning": "regex:^(?[0-9]+)\\.(?[0-9]*[02468])(?\\d*)$" 36 | }, 37 | { 38 | "matchManagers": [ 39 | "npm" 40 | ], 41 | "rangeStrategy": "bump" 42 | } 43 | ], 44 | "postUpdateOptions": [ 45 | "gomodTidy", 46 | "gomodUpdateImportPaths" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /portal/app/models/score.rb: -------------------------------------------------------------------------------- 1 | class Score < ActiveRecord::Base 2 | belongs_to :team 3 | 4 | # トップNチームのスコア 5 | def self.ordered_stats(time:, limit:10) 6 | scores = where('created_at <= ?', time) 7 | .group('team_id') 8 | .order('best_score DESC') 9 | .limit(limit) 10 | .pluck('team_id', 'MAX(score) AS best_score') 11 | 12 | # Pre-fetch teams (speed) 13 | team_hash = Team.pluck(:id, :name).to_h 14 | 15 | score_hash = {} 16 | scores 17 | .each do |(team_id, best_score)| 18 | key = team_hash[team_id] 19 | score_hash[key] = best_score 20 | end 21 | score_hash 22 | end 23 | 24 | def self.history(time:) 25 | # Pre-fetch teams (speed) 26 | team_hash = Team.pluck(:id, :name).to_h 27 | 28 | team_scores = Hash.new {|h,key| h[key] = []} 29 | 30 | where('created_at <= ?', time) 31 | .order('created_at ASC') # インデックス貼ったほうがいいかも 32 | .each do |score| 33 | team_scores[team_hash[score.team_id]] << [score.created_at.in_time_zone, score.score] 34 | end 35 | 36 | # map for graph 37 | # [ 38 | # { name: team1.name, data: [['06:00:00', 123], ['06:05:00', 456]]}, 39 | # { name: team2.name, data: [['06:00:00', 234], ['06:05:00', 345]]}, 40 | # ... 41 | # ] 42 | team_scores.map { |k, v| { name: k, data: v } } 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /portal/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Isucon6WorkerQueue 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | 23 | # benchmaker 24 | config.x.benchmarker.command = '/opt/go/bin/benchmarker' 25 | config.x.benchmarker.timeout = 120 26 | config.x.benchmarker.userdata = '/opt/go/src/github.com/catatsuy/private-isu/benchmarker/userdata' 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /webapp/ruby/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | base64 (0.3.0) 5 | bigdecimal (4.0.1) 6 | connection_pool (3.0.2) 7 | dalli (3.2.8) 8 | kgio (2.11.4) 9 | logger (1.7.0) 10 | multi_json (1.17.0) 11 | mustermann (3.0.4) 12 | ruby2_keywords (~> 0.0.1) 13 | mysql2 (0.5.7) 14 | bigdecimal 15 | rack (3.2.4) 16 | rack-flash3 (1.0.5) 17 | rack 18 | rack-protection (4.2.1) 19 | base64 (>= 0.1.0) 20 | logger (>= 1.6.0) 21 | rack (>= 3.0.0, < 4) 22 | rack-session (2.1.1) 23 | base64 (>= 0.1.0) 24 | rack (>= 3.0.0) 25 | raindrops (0.20.1) 26 | ruby2_keywords (0.0.5) 27 | sinatra (4.2.1) 28 | logger (>= 1.6.0) 29 | mustermann (~> 3.0) 30 | rack (>= 3.0.0, < 4) 31 | rack-protection (= 4.2.1) 32 | rack-session (>= 2.0.0, < 3) 33 | tilt (~> 2.0) 34 | sinatra-contrib (4.2.1) 35 | multi_json (>= 0.0.2) 36 | mustermann (~> 3.0) 37 | rack-protection (= 4.2.1) 38 | sinatra (= 4.2.1) 39 | tilt (~> 2.0) 40 | tilt (2.6.1) 41 | unicorn (6.1.0) 42 | kgio (~> 2.6) 43 | raindrops (~> 0.7) 44 | 45 | PLATFORMS 46 | aarch64-linux 47 | x86_64-linux 48 | 49 | DEPENDENCIES 50 | bigdecimal 51 | connection_pool 52 | dalli 53 | mysql2 54 | rack 55 | rack-flash3 56 | sinatra 57 | sinatra-contrib 58 | unicorn 59 | 60 | BUNDLED WITH 61 | 2.6.2 62 | -------------------------------------------------------------------------------- /portal/app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %> 9 |
10 | 11 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 12 |
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
13 | <% end %> 14 | 15 |
16 | <%= f.label :password %> (leave blank if you don't want to change it)
17 | <%= f.password_field :password, autocomplete: "off" %> 18 |
19 | 20 |
21 | <%= f.label :password_confirmation %>
22 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 23 |
24 | 25 |
26 | <%= f.label :current_password %> (we need your current password to confirm your changes)
27 | <%= f.password_field :current_password, autocomplete: "off" %> 28 |
29 | 30 |
31 | <%= f.submit "Update" %> 32 |
33 | <% end %> 34 | 35 |

Cancel my account

36 | 37 |

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

38 | 39 | <%= link_to "Back", :back %> 40 | -------------------------------------------------------------------------------- /webapp/golang/templates/post.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | {{ .Body }} 14 |
15 |
16 |
17 | comments: {{ .CommentCount }} 18 |
19 | 20 | {{ range .Comments }} 21 |
22 | 23 | {{.Comment}} 24 |
25 | {{ end }} 26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /public_manual.md: -------------------------------------------------------------------------------- 1 | # 社内ISUCON 当日レギュレーション 2 | 3 | ## 禁止事項 4 | 5 | 以下の事項は特別に禁止する。 6 | 7 | * 他のチームへの妨害と主催者がみなす全ての行為 8 | 9 | ## サーバ事項 10 | 11 | 参加者は主催者が用意したAmazon Web Services(以下AWS)のEC2インスタンスを1台利用する。 12 | 13 | ## ソフトウェア事項 14 | 15 | コンテストにあたり、参加者は与えられたソフトウェア、もしくは自分で競技時間内に実装したソフトウェアを用いる。 16 | 17 | 高速化対象のソフトウェアとして主催者からRuby, PHPによるWebアプリケーションが与えられる。ただし各々の性能が一致することを主催者は保証しない。どれをベースに用いてもよいし、独自で実装したものを用いてもよい。 18 | 19 | 競技における高速化対象のアプリケーションとして与えられたアプリケーションから、以下の機能は変更しないこと。 20 | 21 | * アクセス先のURI(ポート、およびHTTPリクエストパス) 22 | * レスポンス(HTML)のDOM構造 23 | * JavaScript/CSSファイルの内容 24 | * 画像および動画等のメディアファイルの内容 25 | 26 | 各サーバにおけるソフトウェアの入れ替え、設定の変更、アプリケーションコードの変更および入れ替えなどは一切禁止しない。起動したインスタンス以外の外部リソースを利用する行為(他のインスタンスに処理を委譲するなど)は禁止する。 27 | 28 | 許可される事項には、例として以下のような作業が含まれる。 29 | 30 | * DBスキーマの変更やインデックスの作成・削除 31 | * キャッシュ機構の追加、jobqueue機構の追加による遅延書き込み 32 | * 他の言語による再実装 33 | 34 | ただし以下の事項に留意すること。 35 | 36 | * コンテスト進行用のメンテナンスコマンドが正常に動作するよう互換性を保つこと 37 | * 各サーバの設定およびデータ構造は任意のタイミングでのサーバ再起動に耐えること 38 | * サーバ再起動後にすべてのアプリケーションコードが正常動作する状態を維持すること 39 | * ベンチマーク実行時にアプリケーションに書き込まれたデータは再起動後にも取得できること 40 | 41 | # 採点 42 | 43 | 採点は採点条件(後述)をクリアした参加者の間で、性能値(後述)の高さを競うものとする。 44 | 45 | 採点条件として、以下の各チェックの検査を通過するものとする。 46 | 47 | * 負荷走行中、POSTしたデータが、POSTへのHTTPレスポンスを返してから即座に関連するURI GETのレスポンスデータに反映されていること 48 | * レスポンスHTMLのDOM構造が変化していないこと 49 | * ブラウザから対象アプリケーションにアクセスした結果、ページ上の表示および各種動作が正常であること 50 | 51 | 性能値として、以下の指標を用いる。 52 | 53 | * 計測ツールの実行時間は1分間とする 54 | * 細かい閾値ならびに配点についての詳細は当日のマニュアルに記載する 55 | * 計測時間内のHTTPリクエスト成功数をベースとする 56 | * リクエストの種類毎に配点を変更する 57 | * エラーの数により減点する 58 | -------------------------------------------------------------------------------- /portal/app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | */ 14 | @import "bootstrap-sprockets"; 15 | @import "bootstrap"; 16 | 17 | 18 | .clickable { 19 | cursor: pointer; 20 | } 21 | 22 | .clickable:hover { 23 | text-decoration: underline; 24 | } 25 | 26 | .job-exec-status { 27 | font-weight: bold; 28 | color: #ffffff; 29 | weight: 20px; 30 | height: 20px; 31 | padding: 5px; 32 | margin: 0 5px; 33 | } 34 | 35 | .job-running { 36 | animation-duration: 1s; 37 | animation-name: blink; 38 | animation-iteration-count: infinite; 39 | animation-direction: alternate; 40 | } 41 | 42 | @keyframes blink { 43 | from { 44 | background-color: rgba(1, 104, 179, 0.5); 45 | } 46 | to { 47 | background-color: rgba(1, 104, 179, 1); 48 | } 49 | } 50 | 51 | .job-waiting { 52 | background-color: #e8e6f3; 53 | } 54 | 55 | .job-my-team { 56 | color: gold; 57 | } 58 | -------------------------------------------------------------------------------- /provisioning/image/ansible/05_app.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: guests:extras 3 | become: yes 4 | become_user: isucon 5 | gather_facts: no 6 | vars: 7 | app_dir: "/home/isucon/private_isu" 8 | tmp_dir: "/home/isucon/private_isu_tmp" 9 | repo_url: "https://github.com/catatsuy/private-isu.git" 10 | tasks: 11 | - name: mkdir backup 12 | file: path=/home/isucon/backup/ state=directory # As per instruction, can remain 13 | - name: Ensure application directory exists 14 | file: 15 | path: "{{ app_dir }}" 16 | state: directory 17 | owner: isucon 18 | group: isucon 19 | mode: '0755' 20 | - name: Clone repository to temporary directory 21 | git: 22 | repo: "{{ repo_url }}" 23 | dest: "{{ tmp_dir }}" 24 | depth: 1 25 | force: yes # force checkout to ensure clean state if tmp_dir somehow exists 26 | - name: Copy webapp to application directory 27 | copy: 28 | src: "{{ tmp_dir }}/webapp/" 29 | dest: "{{ app_dir }}/webapp/" 30 | remote_src: yes 31 | owner: isucon 32 | group: isucon 33 | - name: Conditionally copy benchmarker to application directory 34 | copy: 35 | src: "{{ tmp_dir }}/benchmarker/" 36 | dest: "{{ app_dir }}/benchmarker/" 37 | remote_src: yes 38 | owner: isucon 39 | group: isucon 40 | when: allinone is defined and allinone 41 | - name: Remove temporary clone directory 42 | file: 43 | path: "{{ tmp_dir }}" 44 | state: absent 45 | -------------------------------------------------------------------------------- /provisioning/image/ansible/00_base.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: guests:extras 3 | become: yes 4 | gather_facts: no 5 | tasks: 6 | - apt: autoclean=yes 7 | - apt: update_cache=yes 8 | - apt: upgrade=dist 9 | - apt: name=build-essential state=present 10 | - apt: name=git state=present 11 | - apt: name=gcc state=present 12 | - apt: name=patch state=present 13 | - apt: name=autoconf state=present 14 | - apt: name=automake state=present 15 | - apt: name=bison state=present 16 | - apt: name=libssl-dev state=present 17 | - apt: name=libyaml-dev state=present 18 | - apt: name=libreadline6-dev state=present 19 | - apt: name=zlib1g-dev state=present 20 | - apt: name=libncurses5-dev state=present 21 | - apt: name=libffi-dev state=present 22 | - apt: name=libgdbm6 state=present 23 | - apt: name=libgdbm-dev state=present 24 | - apt: name=libmysqlclient-dev state=present 25 | - apt: name=libpq-dev state=present 26 | - apt: name=libcurl4-openssl-dev state=present 27 | - apt: name=libpng16-16 28 | - apt: name=libpng-dev state=present 29 | - apt: name=libmcrypt4 state=present 30 | - apt: name=libmcrypt-dev state=present 31 | - apt: name=libtidy5deb1 state=present 32 | - apt: name=libtidy-dev state=present 33 | - apt: name=libxml2-dev state=present 34 | - apt: name=libxslt1-dev state=present 35 | - apt: name=readline-common state=present 36 | # for node-install 37 | - apt: name=curl state=present 38 | # for php --with-openssl 39 | - apt: name=pkg-config 40 | - apt: name=acl 41 | -------------------------------------------------------------------------------- /portal/app/jobs/benchmarker_job.rb: -------------------------------------------------------------------------------- 1 | class BenchmarkerJob < ActiveJob::Base 2 | queue_as :default 3 | 4 | DEFAULT_TIMEOUT = 100 5 | 6 | def perform(job_id:) 7 | buf = '' 8 | pid = nil 9 | process = nil 10 | 11 | job = Job.find(job_id) 12 | job.status = 'Running' 13 | job.save 14 | 15 | timeout = Rails.application.config.x.benchmarker.timeout || DEFAULT_TIMEOUT 16 | command = Rails.application.config.x.benchmarker.command 17 | userdata = Rails.application.config.x.benchmarker.userdata 18 | 19 | path = File.dirname(Rails.application.config.x.benchmarker.command) 20 | args = ['-t', job.team.app_host, '-u', userdata].join(' ') 21 | 22 | logger.info "Command: #{command} #{args}" 23 | Timeout.timeout(timeout) do 24 | process = IO.popen("#{command} #{args}") 25 | pid = process.pid 26 | while line = process.gets 27 | buf << line 28 | end 29 | end 30 | logger.info "Result Buffer: #{buf}" 31 | result = JSON.parse(buf) 32 | 33 | job.team.scores << Score.create( 34 | pass: result['pass'], 35 | score: result['score'], 36 | message: result['messages'].join(' ') 37 | ) 38 | rescue Timeout::Error => e 39 | Process.kill('SIGINT', pid) if pid 40 | process.close if process 41 | job.team.scores << Score.create( 42 | pass: 'FAIL', 43 | score: 0, 44 | message: 'TIMEOUT' 45 | ) 46 | rescue => e 47 | logger.info "Unexpected error: #{e.to_s}" 48 | ensure 49 | if job 50 | job.status = 'Finished' 51 | job.save 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /webapp/docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: private-isu 2 | services: 3 | nginx: 4 | image: nginx:1.28 5 | depends_on: 6 | - app 7 | volumes: 8 | - ./etc/nginx/conf.d:/etc/nginx/conf.d 9 | - ./public:/public 10 | ports: 11 | - "80:80" 12 | networks: 13 | - my_network 14 | 15 | app: 16 | build: 17 | # Go実装の場合は golang/ PHP実装の場合は php/ Python実装の場合は python/ に変更 18 | context: ruby/ 19 | dockerfile: Dockerfile 20 | depends_on: 21 | - mysql 22 | - memcached 23 | environment: 24 | ISUCONP_DB_HOST: mysql 25 | ISUCONP_DB_PORT: 3306 26 | ISUCONP_DB_USER: root 27 | ISUCONP_DB_PASSWORD: root 28 | ISUCONP_DB_NAME: isuconp 29 | ISUCONP_MEMCACHED_ADDRESS: memcached:11211 30 | networks: 31 | - my_network 32 | volumes: 33 | - ./public:/home/public 34 | init: true 35 | deploy: 36 | resources: 37 | limits: 38 | cpus: "1" 39 | memory: 1g 40 | 41 | mysql: 42 | image: mysql:8.4 43 | environment: 44 | #- "TZ=Asia/Tokyo" 45 | - "MYSQL_ROOT_HOST=%" 46 | - "MYSQL_ROOT_PASSWORD=root" 47 | volumes: 48 | - mysql:/var/lib/mysql 49 | - ./sql:/docker-entrypoint-initdb.d 50 | ports: 51 | - "3306:3306" 52 | networks: 53 | - my_network 54 | deploy: 55 | resources: 56 | limits: 57 | cpus: "1" 58 | memory: 1g 59 | 60 | memcached: 61 | image: memcached:1.6 62 | networks: 63 | - my_network 64 | 65 | volumes: 66 | mysql: 67 | 68 | networks: 69 | my_network: 70 | -------------------------------------------------------------------------------- /webapp/python/templates/post.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | {{ post.body | nl2br }} 14 |
15 |
16 |
17 | comments: {{ post.comment_count }} 18 |
19 | 20 | {% for comment in post['comments'] %} 21 |
22 | 23 | {{ comment['comment'] }} 24 |
25 | {% endfor %} 26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /webapp/node/views/_post.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | <%- formatBody(post.body) %> 14 |
15 |
16 |
17 | comments: <%= post.comment_count %> 18 |
19 | 20 | <% post.comments.forEach(function(comment) { %> 21 |
22 | 23 | <%= comment.comment %> 24 |
25 | <% }) %> 26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /benchmarker/score/fail.go: -------------------------------------------------------------------------------- 1 | package score 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "sync" 7 | ) 8 | 9 | type failErrors struct { 10 | sync.RWMutex 11 | errs []error 12 | } 13 | 14 | var failInstance *failErrors 15 | var failOnce sync.Once 16 | 17 | func GetFailErrorsInstance() *failErrors { 18 | failOnce.Do(func() { 19 | errs := make([]error, 0) 20 | failInstance = &failErrors{errs: errs} 21 | }) 22 | 23 | return failInstance 24 | } 25 | 26 | func GetFailErrors() []error { 27 | sort.Sort(GetFailErrorsInstance()) 28 | var tmp string 29 | retErrs := make([]error, 0) 30 | 31 | // 適当にuniqする 32 | for _, e := range failInstance.errs { 33 | if tmp != e.Error() { 34 | tmp = e.Error() 35 | retErrs = append(retErrs, e) 36 | } 37 | } 38 | return retErrs 39 | } 40 | 41 | func GetFailRawErrors() []error { 42 | return GetFailErrorsInstance().errs 43 | } 44 | 45 | func GetFailErrorsStringSlice() []string { 46 | msgs := []string{} 47 | for _, err := range GetFailErrors() { 48 | msgs = append(msgs, fmt.Sprint(err.Error())) 49 | } 50 | return msgs 51 | } 52 | 53 | func GetFailRawErrorsStringSlice() []string { 54 | msgs := []string{} 55 | for _, err := range GetFailRawErrors() { 56 | msgs = append(msgs, fmt.Sprint(err.Error())) 57 | } 58 | return msgs 59 | } 60 | 61 | func (fes failErrors) Len() int { 62 | return len(fes.errs) 63 | } 64 | 65 | func (fes failErrors) Swap(i, j int) { 66 | fes.errs[i], fes.errs[j] = fes.errs[j], fes.errs[i] 67 | } 68 | 69 | func (fes failErrors) Less(i, j int) bool { 70 | return fes.errs[i].Error() < fes.errs[j].Error() 71 | } 72 | 73 | func (fes *failErrors) Append(e error) { 74 | fes.Lock() 75 | fes.errs = append(fes.errs, e) 76 | fes.Unlock() 77 | } 78 | -------------------------------------------------------------------------------- /portal/db/migrate/20160310140544_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table(:users) do |t| 4 | ## Database authenticatable 5 | t.string :email, null: false, default: "", limit: 191 6 | t.string :encrypted_password, null: false, default: "", limit: 191 7 | 8 | t.string :name, null: false, default: '', limit: 191 9 | 10 | ## Recoverable 11 | t.string :reset_password_token, limit: 191 12 | t.datetime :reset_password_sent_at 13 | 14 | ## Rememberable 15 | t.datetime :remember_created_at 16 | 17 | ## Trackable 18 | t.integer :sign_in_count, default: 0, null: false 19 | t.datetime :current_sign_in_at 20 | t.datetime :last_sign_in_at 21 | t.string :current_sign_in_ip, limit: 191 22 | t.string :last_sign_in_ip, limit: 191 23 | 24 | ## Confirmable 25 | # t.string :confirmation_token 26 | # t.datetime :confirmed_at 27 | # t.datetime :confirmation_sent_at 28 | # t.string :unconfirmed_email # Only if using reconfirmable 29 | 30 | ## Lockable 31 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 32 | # t.string :unlock_token # Only if unlock strategy is :email or :both 33 | # t.datetime :locked_at 34 | 35 | t.references :team 36 | 37 | t.timestamps null: false 38 | end 39 | 40 | add_index :users, :email, unique: true 41 | add_index :users, :reset_password_token, unique: true 42 | # add_index :users, :confirmation_token, unique: true 43 | # add_index :users, :unlock_token, unique: true 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /portal/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /portal/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | # Adds additional error checking when serving assets at runtime. 35 | # Checks for improperly declared sprockets dependencies. 36 | # Raises helpful error messages. 37 | config.assets.raise_runtime_errors = true 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | end 42 | -------------------------------------------------------------------------------- /webapp/golang/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM --platform=$BUILDPLATFORM golang:1.25 AS build 4 | WORKDIR /src 5 | 6 | RUN --mount=type=cache,target=/go/pkg/mod/ \ 7 | --mount=type=bind,source=go.sum,target=go.sum \ 8 | --mount=type=bind,source=go.mod,target=go.mod \ 9 | go mod download -x 10 | 11 | # This is the architecture you’re building for, which is passed in by the builder. 12 | # Placing it here allows the previous steps to be cached across architectures. 13 | ARG TARGETARCH 14 | 15 | # Build the application. 16 | # Leverage a cache mount to /go/pkg/mod/ to speed up subsequent builds. 17 | # Leverage a bind mount to the current directory to avoid having to copy the 18 | # source code into the container. 19 | RUN --mount=type=cache,target=/go/pkg/mod/ \ 20 | --mount=type=bind,target=. \ 21 | CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /bin/server . 22 | 23 | ################################################################################ 24 | FROM debian:bookworm-slim AS final 25 | 26 | RUN \ 27 | --mount=type=cache,target=/var/lib/apt,sharing=locked \ 28 | --mount=type=cache,target=/var/cache/apt,sharing=locked \ 29 | apt-get update -qq && apt-get install -y openssl 30 | 31 | # Create a non-privileged user that the app will run under. 32 | # See https://docs.docker.com/go/dockerfile-user-best-practices/ 33 | ARG UID=10001 34 | RUN adduser \ 35 | --disabled-password \ 36 | --gecos "" \ 37 | --home "/nonexistent" \ 38 | --shell "/sbin/nologin" \ 39 | --no-create-home \ 40 | --uid "${UID}" \ 41 | appuser 42 | USER appuser 43 | 44 | WORKDIR /home/webapp 45 | 46 | # Copy the executable from the "build" stage. 47 | COPY --from=build /bin/server /bin/ 48 | COPY ./templates ./templates 49 | 50 | # What the container should run when it is started. 51 | ENTRYPOINT [ "/bin/server" ] 52 | -------------------------------------------------------------------------------- /portal/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /webapp/ruby/views/post.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | <%= escape_html(post[:body]).gsub(/\r?\n/, '
') %> 14 |
15 |
16 |
17 | comments: <%= escape_html(post[:comment_count]) %> 18 |
19 | 20 | <% post[:comments].each do |comment| %> 21 |
22 | 23 | <%= escape_html(comment[:comment]) %> 24 |
25 | <% end %> 26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /webapp/public/js/timeago.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).timeago={})}(this,function(e){"use strict";var r=["second","minute","hour","day","week","month","year"];var a=["秒","分钟","小时","天","周","个月","年"];function t(e,t){n[e]=t}function i(e){return n[e]||n.en_US}var n={},f=[60,60,24,7,365/7/12,12];function o(e){return e instanceof Date?e:!isNaN(e)||/^\d+$/.test(e)?new Date(parseInt(e)):(e=(e||"").trim().replace(/\.\d+/,"").replace(/-/,"/").replace(/-/,"/").replace(/(\d)T(\d)/,"$1 $2").replace(/Z/," UTC").replace(/([+-]\d\d):?(\d\d)/," $1$2"),new Date(e))}function d(e,t){for(var n=e<0?1:0,r=e=Math.abs(e),a=0;e>=f[a]&&a=f[n]&&n 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /portal/config/deploy.rb: -------------------------------------------------------------------------------- 1 | # config valid only for current version of Capistrano 2 | lock '3.4.0' 3 | 4 | set :application, 'isucon_portal' 5 | set :repo_url, 'git@github.com:catatsuy/private-isu.git' 6 | 7 | # deploy sub directory 8 | set :repo_tree, 'portal' 9 | 10 | set :user, 'isucon' 11 | set :branch, 'master' 12 | 13 | set :default_env, { path: "/home/isucon/.rbenv/bin:/home/isucon/.rbenv/shims:$PATH" } 14 | set :bundle_jobs, 8 15 | 16 | set :puma_init_active_record, true 17 | set :puma_bind, %w(tcp://127.0.0.1:8080 unix:///tmp/puma.sock) 18 | set :sidekiq_role, :batch 19 | 20 | # Default branch is :master 21 | # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp 22 | 23 | # Default deploy_to directory is /var/www/my_app_name 24 | # set :deploy_to, '/var/www/my_app_name' 25 | 26 | # Default value for :scm is :git 27 | # set :scm, :git 28 | 29 | # Default value for :format is :pretty 30 | # set :format, :pretty 31 | 32 | # Default value for :log_level is :debug 33 | # set :log_level, :debug 34 | 35 | # Default value for :pty is false 36 | # set :pty, true 37 | 38 | # Default value for :linked_files is [] 39 | # set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml') 40 | 41 | # Default value for linked_dirs is [] 42 | set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system') 43 | 44 | # Default value for default_env is {} 45 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } 46 | 47 | # Default value for keep_releases is 5 48 | # set :keep_releases, 5 49 | 50 | namespace :deploy do 51 | 52 | after :restart, :clear_cache do 53 | on roles(:web), in: :groups, limit: 3, wait: 10 do 54 | # Here we can do anything such as: 55 | # within release_path do 56 | # execute :rake, 'cache:clear' 57 | # end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /portal/app/views/home/index.erb: -------------------------------------------------------------------------------- 1 | 2 |

Portal

3 | 4 |

ベンチマークの実行状況

5 | 6 |

数字はキューの順番です。

7 |

現在実行中のベンチマークは青く点滅します。

8 |

自分のチームの順番は黄色く表示されます。

9 | 10 | <% @jobs.each.with_index do |j,i| %> 11 | <%= sprintf("%02d", i+1) %> 12 | <% end %> 13 | 14 |
15 | <% if current_user.team.jobs.any?(&:enqueued?) %> 16 | <%= button_to 'Run Benchmarker', jobs_path, method: :post, class: 'btn btn-primary btn-lg center-block disabled', disabled: true %> 17 | <% else %> 18 | <%= button_to 'Run Benchmarker', jobs_path, method: :post, class: 'btn btn-primary btn-lg center-block' %> 19 | <% end %> 20 |
21 | 22 |

各チームのスコア

23 | 24 |
25 | <%= line_chart @chart_data %> 26 |
27 | 28 |

トップスコア

29 | 30 |
31 | <%= bar_chart @ordered_score %> 32 |
33 | 34 |

あなたのチームのスコア

35 | 36 | <% unless @my_scores.empty? %> 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | <% @my_scores.each do |score| %> 45 | 46 | 47 | 48 | 49 | 58 | 59 | <% end %> 60 |
時刻PASS/FAILスコアメッセージ
<%= score.created_at.in_time_zone('Tokyo').strftime('%H:%M:%S') %><%= score.pass ? 'PASS' : 'FAIL' %><%= score.score %> 50 | <% if score.message.length > 20 %> 51 | 52 | <%= score.message.slice(0, 20) + '...' %> 53 | 54 | <% else %> 55 | <%= score.message %> 56 | <% end %> 57 |
61 | <% end %> 62 | -------------------------------------------------------------------------------- /webapp/php/views/post.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 |
17 | comments: 18 |
19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /webapp/public/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // cf: https://github.com/hustcc/timeago.js 4 | timeago.register('ja', (number, index) => { 5 | return [ 6 | ['すこし前', 'すぐに'], 7 | ['%s秒前', '%s秒以内'], 8 | ['1分前', '1分以内'], 9 | ['%s分前', '%s分以内'], 10 | ['1時間前', '1時間以内'], 11 | ['%s時間前', '%s時間以内'], 12 | ['1日前', '1日以内'], 13 | ['%s日前', '%s日以内'], 14 | ['1週間前', '1週間以内'], 15 | ['%s週間前', '%s週間以内'], 16 | ['1ヶ月前', '1ヶ月以内'], 17 | ['%sヶ月前', '%sヶ月以内'], 18 | ['1年前', '1年以内'], 19 | ['%s年前', '%s年以内'], 20 | ][index]; 21 | }) 22 | 23 | document.addEventListener('DOMContentLoaded', () => { 24 | timeago.render(document.querySelectorAll('time.timeago'), 'ja'); 25 | 26 | const btn = document.getElementById('isu-post-more-btn'); 27 | const postMore = document.getElementById('isu-post-more'); 28 | 29 | if (!btn) { 30 | return; 31 | } 32 | 33 | btn.addEventListener('click', () => { 34 | postMore.classList.add('loading'); 35 | const posts = document.querySelectorAll('.isu-post'); 36 | const lastEl = posts[posts.length-1]; 37 | const maxCreatedAt = lastEl.dataset.createdAt; 38 | fetch(`/posts?max_created_at=${encodeURIComponent(maxCreatedAt)}`, { 39 | method: 'GET', 40 | }).then(response => { 41 | if (!response.ok) { 42 | throw new Error('Network response was not ok'); 43 | } 44 | return response.text(); 45 | }).then(text => { 46 | const parser = new DOMParser(); 47 | const doc = parser.parseFromString(text, "text/html"); 48 | doc.querySelectorAll('.isu-post').forEach((el) => { 49 | const id = el.getAttribute('id'); 50 | if (!document.getElementById(id)) { 51 | lastEl.parentElement.append(el); 52 | } 53 | }); 54 | timeago.render(document.querySelectorAll('time.timeago'), 'ja'); 55 | postMore.classList.remove('loading'); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /portal/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /benchmarker/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM --platform=$BUILDPLATFORM golang:1.25 AS build 4 | WORKDIR /src 5 | 6 | RUN --mount=type=cache,target=/go/pkg/mod/ \ 7 | --mount=type=bind,source=go.sum,target=go.sum \ 8 | --mount=type=bind,source=go.mod,target=go.mod \ 9 | go mod download -x 10 | 11 | # This is the architecture you’re building for, which is passed in by the builder. 12 | # Placing it here allows the previous steps to be cached across architectures. 13 | ARG TARGETARCH 14 | 15 | # Build the application. 16 | # Leverage a cache mount to /go/pkg/mod/ to speed up subsequent builds. 17 | # Leverage a bind mount to the current directory to avoid having to copy the 18 | # source code into the container. 19 | RUN --mount=type=cache,target=/go/pkg/mod/ \ 20 | --mount=type=bind,target=. \ 21 | CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /bin/benchmarker . 22 | 23 | ################################################################################ 24 | FROM alpine:3.23 AS final 25 | 26 | # Install any runtime dependencies that are needed to run your application. 27 | # Leverage a cache mount to /var/cache/apk/ to speed up subsequent builds. 28 | RUN --mount=type=cache,target=/var/cache/apk \ 29 | apk --update add \ 30 | ca-certificates \ 31 | tzdata \ 32 | && \ 33 | update-ca-certificates 34 | 35 | # Create a non-privileged user that the app will run under. 36 | # See https://docs.docker.com/go/dockerfile-user-best-practices/ 37 | ARG UID=10001 38 | RUN adduser \ 39 | --disabled-password \ 40 | --gecos "" \ 41 | --home "/nonexistent" \ 42 | --shell "/sbin/nologin" \ 43 | --no-create-home \ 44 | --uid "${UID}" \ 45 | appuser 46 | USER appuser 47 | 48 | # Copy the executable from the "build" stage. 49 | COPY --from=build /bin/benchmarker /bin/ 50 | 51 | COPY run.sh /opt/ 52 | COPY userdata /opt/userdata 53 | 54 | # What the container should run when it is started. 55 | ENTRYPOINT [ "/opt/run.sh" ] 56 | -------------------------------------------------------------------------------- /portal/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | devise_for :users 3 | root to: "home#index" 4 | 5 | resources :jobs, only: [:create] 6 | 7 | # The priority is based upon order of creation: first created -> highest priority. 8 | # See how all your routes lay out with "rake routes". 9 | 10 | # You can have the root of your site routed with "root" 11 | # root 'welcome#index' 12 | 13 | # Example of regular route: 14 | # get 'products/:id' => 'catalog#view' 15 | 16 | # Example of named route that can be invoked with purchase_url(id: product.id) 17 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase 18 | 19 | # Example resource route (maps HTTP verbs to controller actions automatically): 20 | # resources :products 21 | 22 | # Example resource route with options: 23 | # resources :products do 24 | # member do 25 | # get 'short' 26 | # post 'toggle' 27 | # end 28 | # 29 | # collection do 30 | # get 'sold' 31 | # end 32 | # end 33 | 34 | # Example resource route with sub-resources: 35 | # resources :products do 36 | # resources :comments, :sales 37 | # resource :seller 38 | # end 39 | 40 | # Example resource route with more complex sub-resources: 41 | # resources :products do 42 | # resources :comments 43 | # resources :sales do 44 | # get 'recent', on: :collection 45 | # end 46 | # end 47 | 48 | # Example resource route with concerns: 49 | # concern :toggleable do 50 | # post 'toggle' 51 | # end 52 | # resources :posts, concerns: :toggleable 53 | # resources :photos, concerns: :toggleable 54 | 55 | # Example resource route within a namespace: 56 | # namespace :admin do 57 | # # Directs /admin/products/* to Admin::ProductsController 58 | # # (app/controllers/admin/products_controller.rb) 59 | # resources :products 60 | # end 61 | end 62 | -------------------------------------------------------------------------------- /benchmarker/userdata/ramen/ramen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let jsdom = require('jsdom'); 4 | let moment = require('moment'); 5 | let co = require('co'); 6 | let fs = require('fs'); 7 | let https = require('https'); 8 | 9 | function loadTwilog(i) { 10 | return new Promise((resolve, reject) => { 11 | jsdom.env({ 12 | url: 'http://twilog.org/catatsuy/search?word=pic.twitter.com&ao=a&page=' + i, 13 | done: (err, window) => { 14 | let $ = require('jquery')(window); 15 | let srcList = []; 16 | $('img.tl-image').each((i, img) => { 17 | let src = $(img).attr('src'); 18 | //console.log(src); 19 | if (/jpg:small/.test(src)) { 20 | srcList.push(src.replace(/:small$/, ':large')); 21 | } 22 | }); 23 | resolve(srcList); 24 | } 25 | }); 26 | }); 27 | } 28 | 29 | function loadImage(src) { 30 | return new Promise((resolve, reject) => { 31 | let filename = src.replace(/^.*\/([^\/]+\.jpg):large$/, '$1'); 32 | //console.log(src, filename); 33 | 34 | https.get(src, (response) => { 35 | let file = fs.createWriteStream('img/' + filename); 36 | response.pipe(file); 37 | 38 | file.on('finish', () => { 39 | file.close(() => { 40 | resolve(filename); 41 | }); 42 | }); 43 | file.on('error', (err) => { 44 | console.log(err.message); 45 | reject(err.message); 46 | }) 47 | }).on('error', (err) => { 48 | console.log(err.message); 49 | fs.unlink('img/' + filename); 50 | reject(err.message); 51 | }); 52 | }); 53 | } 54 | 55 | co(function *() { 56 | try { 57 | console.log('loading twilog...'); 58 | 59 | for (let i = 0; i < 20; i++) { 60 | console.log(i); 61 | let srcList = yield loadTwilog(i); 62 | 63 | console.log('loading images...'); 64 | for (let j = 0; j < srcList.length; j++) { 65 | console.log(srcList[j]); 66 | yield loadImage(srcList[j]); 67 | } 68 | } 69 | } catch (e) { 70 | console.log(e); 71 | } 72 | }); 73 | -------------------------------------------------------------------------------- /benchmarker/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | "time" 7 | 8 | "github.com/catatsuy/private-isu/benchmarker/util" 9 | "github.com/marcw/cachecontrol" 10 | ) 11 | 12 | type cacheStore struct { 13 | sync.RWMutex 14 | items map[string]*URLCache 15 | } 16 | 17 | func NewCacheStore() *cacheStore { 18 | m := make(map[string]*URLCache) 19 | c := &cacheStore{ 20 | items: m, 21 | } 22 | return c 23 | } 24 | 25 | func (c *cacheStore) Get(key string) (*URLCache, bool) { 26 | c.RLock() 27 | v, found := c.items[key] 28 | c.RUnlock() 29 | return v, found 30 | } 31 | 32 | func (c *cacheStore) Set(key string, value *URLCache) { 33 | c.Lock() 34 | c.items[key] = value 35 | c.Unlock() 36 | } 37 | 38 | var instance *cacheStore 39 | var once sync.Once 40 | 41 | func GetInstance() *cacheStore { 42 | once.Do(func() { 43 | instance = NewCacheStore() 44 | }) 45 | 46 | return instance 47 | } 48 | 49 | type URLCache struct { 50 | LastModified string 51 | Etag string 52 | ExpiresAt time.Time 53 | CacheControl *cachecontrol.CacheControl 54 | MD5 string 55 | } 56 | 57 | func NewURLCache(res *http.Response) (*URLCache, string) { 58 | directive := res.Header.Get("Cache-Control") 59 | cc := cachecontrol.Parse(directive) 60 | noCache, _ := cc.NoCache() 61 | md5 := util.GetMD5ByIO(res.Body) 62 | 63 | if len(directive) == 0 || noCache || cc.NoStore() { 64 | return nil, md5 65 | } 66 | 67 | now := time.Now() 68 | lm := res.Header.Get("Last-Modified") 69 | etag := res.Header.Get("ETag") 70 | 71 | return &URLCache{ 72 | LastModified: lm, 73 | Etag: etag, 74 | ExpiresAt: now.Add(cc.MaxAge()), 75 | CacheControl: &cc, 76 | MD5: md5, 77 | }, md5 78 | } 79 | 80 | func (c *URLCache) Available() bool { 81 | return time.Now().Before(c.ExpiresAt) 82 | } 83 | 84 | func (c *URLCache) Apply(req *http.Request) { 85 | if c.Available() { 86 | if c.LastModified != "" { 87 | req.Header.Add("If-Modified-Since", c.LastModified) 88 | } 89 | 90 | if c.Etag != "" { 91 | req.Header.Add("If-None-Match", c.Etag) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /webapp/public/css/style.css: -------------------------------------------------------------------------------- 1 | div { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | h1 { 7 | font-size: 2em; 8 | margin: 0; 9 | } 10 | 11 | h1 a { 12 | color: black; 13 | text-decoration: none; 14 | } 15 | 16 | a:hover { 17 | color: red; 18 | } 19 | 20 | .container { 21 | width: 600px; 22 | margin: 0 auto; 23 | } 24 | 25 | .isu-post { 26 | border: 1px solid gray; 27 | margin-bottom: 15px; 28 | padding: 15px; 29 | } 30 | 31 | .isu-post-text { 32 | margin: 10px 15px 15px 15px; 33 | } 34 | 35 | .isu-post-image { 36 | text-align: center; 37 | } 38 | 39 | .isu-image { 40 | max-width: 540px; 41 | max-height: 1000px; 42 | } 43 | 44 | .isu-submit { 45 | margin-bottom: 25px; 46 | } 47 | 48 | textarea { 49 | width: 600px; 50 | height: 200px; 51 | padding: 0; 52 | } 53 | 54 | .isu-form { 55 | margin-top: 10px; 56 | } 57 | 58 | .isu-post-account-name { 59 | color: gray; 60 | font-size: small; 61 | font-weight: bold; 62 | margin-right: 3px; 63 | } 64 | 65 | .isu-comment-account-name { 66 | color: gray; 67 | font-size: small; 68 | font-weight: bold; 69 | margin-right: 2px; 70 | } 71 | 72 | .header { 73 | margin-top: 30px; 74 | margin-bottom: 30px; 75 | height: 60px; 76 | } 77 | 78 | .isu-title { 79 | width: 75%; 80 | float: left; 81 | } 82 | 83 | .isu-header-menu { 84 | width: 25%; 85 | float: left; 86 | text-align: right; 87 | } 88 | 89 | .isu-post-header { 90 | margin: 0 15px 15px; 91 | } 92 | 93 | .isu-post-comment { 94 | margin: 10px 15px 5px 15px; 95 | } 96 | 97 | .isu-post-comment-count { 98 | font-size: small; 99 | color: gray; 100 | } 101 | 102 | .isu-comment-form { 103 | margin-top: 15px; 104 | } 105 | 106 | .isu-register { 107 | margin-top: 15px; 108 | } 109 | 110 | .alert { 111 | margin: 10px 0; 112 | } 113 | 114 | .alert-danger { 115 | font-weight: bold; 116 | color: red; 117 | } 118 | 119 | .isu-user { 120 | text-align: center; 121 | } 122 | 123 | #isu-post-more { 124 | text-align: center; 125 | } 126 | 127 | #isu-post-more.loading #isu-post-more-btn { 128 | display: none; 129 | } 130 | 131 | .isu-loading-icon { 132 | display: none; 133 | } 134 | 135 | #isu-post-more.loading .isu-loading-icon { 136 | display: inline; 137 | } 138 | -------------------------------------------------------------------------------- /webapp/golang/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/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= 4 | github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= 5 | github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304 h1:f/AUyZ4PoqHhBJnhMrrNtSNYH5RvLxr5UQ0qrOZ9jkE= 6 | github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= 7 | github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= 8 | github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 9 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 10 | github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= 11 | github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= 12 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 13 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 14 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 15 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 16 | github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= 17 | github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 18 | github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= 19 | github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= 20 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 21 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 22 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 23 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 24 | github.com/memcachier/mc/v3 v3.0.3 h1:qii+lDiPKi36O4Xg+HVKwHu6Oq+Gt17b+uEiA0Drwv4= 25 | github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug= 26 | -------------------------------------------------------------------------------- /benchmarker/userdata.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/catatsuy/private-isu/benchmarker/checker" 11 | "github.com/catatsuy/private-isu/benchmarker/util" 12 | ) 13 | 14 | func prepareUserdata(userdata string) ([]user, []user, []user, []string, []*checker.Asset, error) { 15 | if userdata == "" { 16 | return nil, nil, nil, nil, nil, errors.New("userdataディレクトリが指定されていません") 17 | } 18 | info, err := os.Stat(userdata) 19 | if err != nil { 20 | return nil, nil, nil, nil, nil, err 21 | } 22 | if !info.IsDir() { 23 | return nil, nil, nil, nil, nil, errors.New("userdataがディレクトリではありません") 24 | } 25 | 26 | file, err := os.Open(userdata + "/names.txt") 27 | if err != nil { 28 | return nil, nil, nil, nil, nil, err 29 | } 30 | defer file.Close() 31 | 32 | users := []user{} 33 | bannedUsers := []user{} 34 | 35 | scanner := bufio.NewScanner(file) 36 | i := 1 37 | for scanner.Scan() { 38 | name := scanner.Text() 39 | if i%50 == 0 { // 50で割れる場合はbanされたユーザー 40 | bannedUsers = append(users, user{AccountName: name, Password: name + name}) 41 | } else { 42 | users = append(users, user{AccountName: name, Password: name + name}) 43 | } 44 | i++ 45 | } 46 | adminUsers := users[:9] 47 | 48 | sentenceFile, err := os.Open(userdata + "/kaomoji.txt") 49 | if err != nil { 50 | return nil, nil, nil, nil, nil, err 51 | } 52 | defer sentenceFile.Close() 53 | 54 | sentences := []string{} 55 | 56 | sScanner := bufio.NewScanner(sentenceFile) 57 | for sScanner.Scan() { 58 | sentence := sScanner.Text() 59 | sentences = append(sentences, sentence) 60 | } 61 | 62 | imgs, err := filepath.Glob(userdata + "/img/000*") // 00001.jpg, 00002.png, 00003.gif など 63 | if err != nil { 64 | return nil, nil, nil, nil, nil, err 65 | } 66 | 67 | images := []*checker.Asset{} 68 | 69 | for _, img := range imgs { 70 | data, err := os.ReadFile(img) 71 | if err != nil { 72 | return nil, nil, nil, nil, nil, err 73 | } 74 | 75 | imgType := "" 76 | if strings.HasSuffix(img, "jpg") { 77 | imgType = "image/jpeg" 78 | } else if strings.HasSuffix(img, "png") { 79 | imgType = "image/png" 80 | } else if strings.HasSuffix(img, "gif") { 81 | imgType = "image/gif" 82 | } 83 | 84 | images = append(images, &checker.Asset{ 85 | MD5: util.GetMD5(data), 86 | Path: img, 87 | Type: imgType, 88 | }) 89 | } 90 | 91 | return users[9:], bannedUsers, adminUsers, sentences, images, err 92 | } 93 | -------------------------------------------------------------------------------- /ansible_old/roles/isucon-base/tasks/ruby.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install dependencies for rbenv 3 | apt: name={{ item }} state=latest 4 | with_items: 5 | - git 6 | 7 | - name: Install rbenv 8 | become: yes 9 | become_user: "{{ rbenv_user }}" 10 | git: repo=https://github.com/sstephenson/rbenv.git dest=~/.rbenv 11 | 12 | - name: Add ~/.rbenv/bin to $PATH 13 | become: yes 14 | become_user: "{{ rbenv_user }}" 15 | lineinfile: > 16 | dest="~/.profile" 17 | line="export PATH=$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH" 18 | 19 | - name: Eval rbenv init in ~/.profile 20 | become: yes 21 | become_user: "{{ rbenv_user }}" 22 | lineinfile: > 23 | dest="~/.profile" 24 | line='eval "$(rbenv init -)"' 25 | 26 | - name: Install dependencies for ruby-build (see. https://github.com/sstephenson/ruby-build/wiki) 27 | apt: name={{ item }} state=latest 28 | with_items: 29 | - autoconf 30 | - bison 31 | - build-essential 32 | - libssl-dev 33 | - libyaml-dev 34 | - libreadline6-dev 35 | - zlib1g-dev 36 | - libncurses5-dev 37 | - libffi-dev 38 | - libgdbm3 39 | - libgdbm-dev 40 | 41 | - name: Install ruby-build as rbenv plugin 42 | become: yes 43 | become_user: "{{ rbenv_user }}" 44 | git: repo=https://github.com/sstephenson/ruby-build.git dest=~/.rbenv/plugins/ruby-build 45 | 46 | - name: Check if version is installed ruby 47 | become: yes 48 | become_user: "{{ rbenv_user }}" 49 | shell: ". ~/.profile && rbenv versions | grep {{ rbenv_ruby_version }}" 50 | register: rbenv_check_install 51 | changed_when: False 52 | ignore_errors: yes 53 | 54 | - name: Install ruby 55 | become: yes 56 | become_user: "{{ rbenv_user }}" 57 | shell: ". ~/.profile && rbenv install {{ rbenv_ruby_version }}" 58 | when: rbenv_check_install|failed 59 | 60 | - name: Check if version is the default ruby version 61 | become: yes 62 | become_user: "{{ rbenv_user }}" 63 | shell: ". ~/.profile && rbenv version | grep {{ rbenv_ruby_version }}" 64 | register: rbenv_check_default 65 | changed_when: False 66 | ignore_errors: yes 67 | 68 | - name: Set default ruby version 69 | become: yes 70 | become_user: "{{ rbenv_user }}" 71 | shell: ". ~/.profile && rbenv global {{ rbenv_ruby_version }}" 72 | when: rbenv_check_default|failed 73 | 74 | - name: install bundler 75 | become: yes 76 | become_user: "{{ rbenv_user }}" 77 | shell: ". ~/.profile && gem install bundler" 78 | args: 79 | creates: "/home/{{ rbenv_user }}/.rbenv/shims/bundle" 80 | -------------------------------------------------------------------------------- /.github/workflows/hadolint.yml: -------------------------------------------------------------------------------- 1 | name: Hadolint Dockerfiles 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "**/Dockerfile" 7 | - ".github/workflows/hadolint.yml" 8 | 9 | jobs: 10 | hadolint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Fetch merge base and ensure availability 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | run: | 21 | BASE_SHA="${{ github.event.pull_request.base.sha }}" 22 | HEAD_SHA="${{ github.event.pull_request.head.sha }}" 23 | echo "Base SHA: ${BASE_SHA}" 24 | echo "Head SHA: ${HEAD_SHA}" 25 | MERGE_BASE_SHA=$(curl -s \ 26 | -H "Authorization: token ${GITHUB_TOKEN}" \ 27 | -H "Accept: application/vnd.github+json" \ 28 | "https://api.github.com/repos/${{ github.repository }}/compare/${BASE_SHA}...${HEAD_SHA}" | jq -r '.merge_base_commit.sha') 29 | if [ "$MERGE_BASE_SHA" = "null" ] || [ -z "$MERGE_BASE_SHA" ]; then 30 | echo "Error: Could not retrieve merge base commit SHA." 31 | exit 1 32 | fi 33 | echo "Merge base commit SHA: ${MERGE_BASE_SHA}" 34 | echo "MERGE_BASE_SHA=${MERGE_BASE_SHA}" >> $GITHUB_ENV 35 | echo "HEAD_SHA=${HEAD_SHA}" >> $GITHUB_ENV 36 | git fetch --depth=1 origin "${MERGE_BASE_SHA}" 37 | 38 | - name: Find changed Dockerfiles 39 | run: | 40 | MERGE_BASE=${{ env.MERGE_BASE_SHA }} 41 | HEAD_SHA=${{ env.HEAD_SHA }} 42 | echo "Using merge base: ${MERGE_BASE}" 43 | CHANGED_DOCKERFILES=$(git diff --name-only "${MERGE_BASE}" "${HEAD_SHA}" -- '**/Dockerfile') 44 | if [ -z "$CHANGED_DOCKERFILES" ]; then 45 | echo "No Dockerfile changes detected; skipping hadolint." 46 | echo "SKIP_LINT=true" >> $GITHUB_ENV 47 | exit 0 48 | fi 49 | echo "Dockerfiles to lint:" 50 | echo "$CHANGED_DOCKERFILES" 51 | { 52 | echo "CHANGED_DOCKERFILES<> "$GITHUB_ENV" 56 | 57 | - name: Lint changed Dockerfiles 58 | if: env.SKIP_LINT != 'true' 59 | run: | 60 | echo "${CHANGED_DOCKERFILES}" | while read -r file; do 61 | [ -z "$file" ] && continue 62 | echo "Linting $file" 63 | docker run --rm -i -v "$PWD:/work" -w /work hadolint/hadolint < "$file" 64 | done 65 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Repository Guidelines 2 | 3 | ## Project Structure & Module Organization 4 | - `webapp/` hosts language ports (`golang/`, `ruby/`, `php/`, `python/`, `node/`); focus changes on one port and mirror fixes only when needed. 5 | - `webapp/sql/` holds schema and fixtures consumed by `make init`; version migrations and data patches here. 6 | - `benchmarker/` contains the Go load tester, while `provisioning/` tracks Ansible roles for operational parity. 7 | 8 | ## Build, Test, and Development Commands 9 | - `make init`: download the canonical MySQL dump and image fixtures. 10 | - `cd webapp/golang && make`: build the Go binary to `./app`; runtime config comes from `ISUCONP_*` env vars. 11 | - `cd webapp/node && npm install && npm run build`: install and transpile the TypeScript service; use `npm run dev` for hot reload. 12 | - `cd webapp && docker compose up`: run nginx, the app tier, MySQL, and Memcached locally. 13 | - `cd benchmarker && make && ./bin/benchmarker -t "http://localhost:8080" -u ./userdata`: rebuild and execute the scorer after optimizations. 14 | 15 | ## Coding Style & Naming Conventions 16 | - Go: enforce `go fmt ./...`; group handlers, repositories, and services by feature under `internal/`. 17 | - Ruby/Python/PHP: follow existing idioms (Ruby 2-space indent, Python PEP 8, PHP PSR-12); avoid mixing tabs and spaces. 18 | - Node/TypeScript: ES modules with strict typing; PascalCase classes, camelCase identifiers; run `tsc` before pushing. 19 | - Shared SQL, template, and static asset filenames stay snake_case; keep uploaded media names space-free. 20 | 21 | ## Testing Guidelines 22 | - Supplement changes with language-native unit tests when touching core logic (`go test ./...`, `pytest`, `bundle exec rspec`, `npm test`) even if suites are sparse. 23 | - Benchmark every performance tweak and note the score delta in your PR description. 24 | - For schema updates, load `webapp/sql/dump.sql` into local MySQL and smoke-test login plus timeline flows. 25 | 26 | ## Commit & Pull Request Guidelines 27 | - Use imperative commit subjects (`optimize timeline query`); dependency bumps often follow `chore(deps):`/`fix(deps):` patterns. 28 | - Keep commits narrowly scoped so regressions are traceable; include schema files and generated assets with the change. 29 | - PRs summarize the bottleneck, the fix, benchmark results, and env var updates; link issues when applicable. 30 | - Attach screenshots or shell snippets for benchmark output or UI adjustments and flag manual deploy steps. 31 | 32 | ## Security & Configuration Tips 33 | - Never commit secrets or dumps; reference required env vars (`ISUCONP_DB_HOST`, etc.) instead. 34 | - Rotate credentials through `provisioning/` and keep sensitive data vaulted. 35 | - Enforce login checks on new endpoints and prefer filesystem-backed images over raw BLOB responses when optimizing. 36 | -------------------------------------------------------------------------------- /portal/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20160319030401) do 15 | 16 | create_table "jobs", force: :cascade do |t| 17 | t.string "status", limit: 255 18 | t.integer "team_id", limit: 4 19 | t.datetime "created_at", null: false 20 | t.datetime "updated_at", null: false 21 | end 22 | 23 | create_table "scores", force: :cascade do |t| 24 | t.boolean "pass" 25 | t.integer "score", limit: 4 26 | t.text "message", limit: 65535 27 | t.integer "team_id", limit: 4 28 | t.datetime "created_at", null: false 29 | t.datetime "updated_at", null: false 30 | end 31 | 32 | create_table "teams", force: :cascade do |t| 33 | t.string "name", limit: 255 34 | t.string "app_host", limit: 255 35 | t.datetime "created_at", null: false 36 | t.datetime "updated_at", null: false 37 | end 38 | 39 | create_table "terms", force: :cascade do |t| 40 | t.string "name", limit: 255, null: false 41 | t.datetime "start_at" 42 | t.datetime "end_at" 43 | t.datetime "created_at", null: false 44 | t.datetime "updated_at", null: false 45 | end 46 | 47 | create_table "users", force: :cascade do |t| 48 | t.string "email", limit: 191, default: "", null: false 49 | t.string "encrypted_password", limit: 191, default: "", null: false 50 | t.string "name", limit: 191, default: "", null: false 51 | t.string "reset_password_token", limit: 191 52 | t.datetime "reset_password_sent_at" 53 | t.datetime "remember_created_at" 54 | t.integer "sign_in_count", limit: 4, default: 0, null: false 55 | t.datetime "current_sign_in_at" 56 | t.datetime "last_sign_in_at" 57 | t.string "current_sign_in_ip", limit: 191 58 | t.string "last_sign_in_ip", limit: 191 59 | t.integer "team_id", limit: 4 60 | t.datetime "created_at", null: false 61 | t.datetime "updated_at", null: false 62 | end 63 | 64 | add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree 65 | add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree 66 | 67 | end 68 | -------------------------------------------------------------------------------- /portal/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = true 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | # config.log_tags = [ :subdomain, :uuid ] 53 | 54 | # Use a different logger for distributed setups. 55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 61 | # config.action_controller.asset_host = 'http://assets.example.com' 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Do not dump schema after migrations. 78 | config.active_record.dump_schema_after_migration = false 79 | 80 | # ActiveJob 81 | config.active_job.queue_adapter = :sidekiq 82 | end 83 | -------------------------------------------------------------------------------- /benchmarker/userdata/load.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'mysql2' 4 | require 'digest' 5 | 6 | rand = Random.new(1) 7 | 8 | def digest(src) 9 | Digest::SHA512.hexdigest(src) 10 | end 11 | 12 | def calculate_salt(account_name) 13 | digest account_name 14 | end 15 | 16 | def calculate_passhash(password, account_name) 17 | digest "#{password}:#{calculate_salt(account_name)}" 18 | end 19 | 20 | 21 | puts "imgディレクトリに画像を展開" 22 | `curl -L -O https://github.com/catatsuy/private-isu/releases/download/img/img.zip` 23 | `unzip img.zip` 24 | 25 | kaomoji = File.read('kaomoji.txt').strip().split("\n") 26 | 27 | db = Mysql2::Client.new( 28 | host: 'localhost', 29 | port: 3306, 30 | username: 'root', 31 | password: '', 32 | database: 'isuconp', 33 | encoding: 'utf8mb4', 34 | reconnect: true, 35 | ) 36 | db.query_options.merge!(symbolize_keys: true) 37 | 38 | 39 | puts "schema.sqlを読み込む" 40 | File.read('../sql/schema.sql').split(';').each do |sql| 41 | puts sql 42 | db.query(sql) unless sql.strip == '' 43 | end 44 | 45 | 46 | #CREATE TABLE IF NOT EXISTS users ( 47 | #`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, 48 | #`account_name` varchar(64) NOT NULL UNIQUE, 49 | #`passhash` varchar(128) NOT NULL, -- SHA2 512 non-binary (hex) 50 | #`authority` tinyint(1) NOT NULL DEFAULT 0, 51 | #`del_flg` tinyint(1) NOT NULL DEFAULT 0, 52 | #`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 53 | #) DEFAULT CHARSET=utf8mb4; 54 | 55 | puts "users" 56 | 57 | query = db.prepare('INSERT INTO users (`id`,`account_name`,`passhash`,`authority`,`del_flg`,`created_at`) VALUES (?,?,?,?,?,?)') 58 | open('names.txt') do |f| 59 | f.each_line.with_index(1) do |line,i| 60 | account_name = line.chomp 61 | password = account_name * 2 62 | 63 | passhash = calculate_passhash(password, account_name) 64 | 65 | authority = i < 10 ? 1 : 0 66 | del_flg = ((i >= 10) && (i % 50 == 0)) ? 1 : 0 67 | created_at = DateTime.parse('2016-01-01 00:00:00') + (1.to_r / 24 / 60 / 60 * i) # 毎秒1アカウント作られたことにする 68 | 69 | query.execute(i, account_name, passhash, authority, del_flg, created_at.to_time) 70 | 71 | p i if i % 20 == 0 72 | end 73 | end 74 | 75 | #CREATE TABLE IF NOT EXISTS posts ( 76 | #`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, 77 | #`user_id` int NOT NULL, 78 | #`mime` varchar(64) NOT NULL, 79 | #`imgdata` mediumblob NOT NULL, 80 | #`body` text NOT NULL, 81 | #`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 82 | #) DEFAULT CHARSET=utf8mb4; 83 | 84 | puts "posts" 85 | 86 | query = db.prepare('INSERT INTO posts (`id`,`user_id`,`mime`,`imgdata`,`body`,`created_at`) VALUES (?,?,?,?,?,?)') 87 | 88 | Dir.glob("img/*").shuffle(random: rand).each.with_index(1) do |image, i| 89 | user_id = rand.rand(1..1000) 90 | mime = image.end_with?('.jpg') ? 'image/jpeg' : image.end_with?('.png') ? 'image/png' : 'image/gif' 91 | created_at = DateTime.parse('2016-01-02 00:00:00') + (1.to_r / 24 / 60 / 60 * i) # 毎秒1投稿されたことにする 92 | body = kaomoji[rand.rand(0...kaomoji.length)] 93 | 94 | open(image) do |f| 95 | query.execute(i, user_id, mime, f.read, body, created_at.to_time) 96 | end 97 | 98 | p i if i % 100 == 0 99 | end 100 | 101 | #CREATE TABLE IF NOT EXISTS comments ( 102 | #`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, 103 | #`post_id` int NOT NULL, 104 | #`user_id` int NOT NULL, 105 | #`comment` text NOT NULL, 106 | #`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 107 | #) DEFAULT CHARSET=utf8mb4; 108 | 109 | puts "comments" 110 | 111 | query = db.prepare('INSERT INTO comments (`id`,`post_id`,`user_id`,`comment`,`created_at`) VALUES (?,?,?,?,?)') 112 | 113 | 1.upto(100_000).each do |i| 114 | post_id = rand.rand(1..10000) 115 | user_id = rand.rand(1..1000) 116 | comment = kaomoji[rand.rand(0...kaomoji.length)] 117 | created_at = DateTime.parse('2016-01-03 00:00:00') + (1.to_r / 24 / 60 / 60 * i) # 毎秒1コメントされたことにする 118 | 119 | query.execute(i, post_id, user_id, comment, created_at.to_time) 120 | end 121 | 122 | puts "mysqldumpを出力して圧縮" 123 | `mysqldump -u root -h localhost --hex-blob --add-drop-database --databases isuconp | bzip2 > dump.sql.bz2` 124 | -------------------------------------------------------------------------------- /portal/config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | password_change: 27 | subject: "Password Changed" 28 | omniauth_callbacks: 29 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 30 | success: "Successfully authenticated from %{kind} account." 31 | passwords: 32 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 33 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 34 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 35 | updated: "Your password has been changed successfully. You are now signed in." 36 | updated_not_active: "Your password has been changed successfully." 37 | registrations: 38 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 39 | signed_up: "Welcome! You have signed up successfully." 40 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 41 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 42 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 43 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." 44 | updated: "Your account has been updated successfully." 45 | sessions: 46 | signed_in: "Signed in successfully." 47 | signed_out: "Signed out successfully." 48 | already_signed_out: "Signed out successfully." 49 | unlocks: 50 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 51 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 52 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 53 | errors: 54 | messages: 55 | already_confirmed: "was already confirmed, please try signing in" 56 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 57 | expired: "has expired, please request a new one" 58 | not_found: "not found" 59 | not_locked: "was not locked" 60 | not_saved: 61 | one: "1 error prohibited this %{resource} from being saved:" 62 | other: "%{count} errors prohibited this %{resource} from being saved:" 63 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["master"] 17 | paths: 18 | - "webapp/**" 19 | - "benchmarker/**" 20 | - "portal/**" 21 | - ".github/workflows/codeql.yml" 22 | pull_request: 23 | branches: ["master"] 24 | paths: 25 | - "webapp/**" 26 | - "benchmarker/**" 27 | - "portal/**" 28 | - ".github/workflows/codeql.yml" 29 | schedule: 30 | - cron: "37 22 * * 0" 31 | 32 | jobs: 33 | analyze: 34 | name: Analyze 35 | # Runner size impacts CodeQL analysis time. To learn more, please see: 36 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 37 | # - https://gh.io/supported-runners-and-hardware-resources 38 | # - https://gh.io/using-larger-runners 39 | # Consider using larger runners for possible analysis time improvements. 40 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 41 | timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} 42 | permissions: 43 | # required for all workflows 44 | security-events: write 45 | 46 | # only required for workflows in private repositories 47 | actions: read 48 | contents: read 49 | 50 | strategy: 51 | fail-fast: false 52 | matrix: 53 | language: ["go", "ruby", "javascript-typescript", "python"] 54 | # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] 55 | # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both 56 | # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 57 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 58 | 59 | steps: 60 | - name: Checkout repository 61 | uses: actions/checkout@v6 62 | 63 | - name: Set up Go 64 | uses: actions/setup-go@v6 65 | with: 66 | go-version: 1.25.5 67 | 68 | # Initializes the CodeQL tools for scanning. 69 | - name: Initialize CodeQL 70 | uses: github/codeql-action/init@v4 71 | with: 72 | languages: ${{ matrix.language }} 73 | # If you wish to specify custom queries, you can do so here or in a config file. 74 | # By default, queries listed here will override any specified in a config file. 75 | # Prefix the list here with "+" to use these queries and those in the config file. 76 | 77 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 78 | # queries: security-extended,security-and-quality 79 | 80 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). 81 | # If this step fails, then you should remove it and run the build manually (see below) 82 | - name: Autobuild 83 | uses: github/codeql-action/autobuild@v4 84 | 85 | # ℹ️ Command-line programs to run using the OS shell. 86 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 87 | 88 | # If the Autobuild fails above, remove it and uncomment the following three lines. 89 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 90 | 91 | # - run: | 92 | # echo "Run, Build Application using script" 93 | # ./location_of_script_within_repo/buildscript.sh 94 | 95 | - name: Perform CodeQL Analysis 96 | uses: github/codeql-action/analyze@v4 97 | with: 98 | category: "/language:${{matrix.language}}" 99 | -------------------------------------------------------------------------------- /benchmarker/checker/session.go: -------------------------------------------------------------------------------- 1 | package checker 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "log" 8 | "mime/multipart" 9 | "net/http" 10 | "net/http/cookiejar" 11 | "net/textproto" 12 | "net/url" 13 | "os" 14 | "path/filepath" 15 | "strings" 16 | "time" 17 | 18 | "github.com/catatsuy/private-isu/benchmarker/score" 19 | ) 20 | 21 | const ( 22 | UserAgent = "benchmarker" 23 | ) 24 | 25 | var ( 26 | targetHost *url.URL 27 | ) 28 | 29 | type Session struct { 30 | Client *http.Client 31 | Transport *http.Transport 32 | 33 | logger *log.Logger 34 | } 35 | 36 | func NewSession() *Session { 37 | w := &Session{ 38 | logger: log.New(os.Stdout, "", 0), 39 | } 40 | 41 | jar, _ := cookiejar.New(&cookiejar.Options{}) 42 | w.Transport = &http.Transport{} 43 | w.Client = &http.Client{ 44 | Transport: w.Transport, 45 | Jar: jar, 46 | Timeout: time.Duration(10) * time.Second, 47 | } 48 | 49 | return w 50 | } 51 | 52 | func SetTargetHost(host string) (*url.URL, error) { 53 | parsedURL, err := urlParse(host) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | targetHost = parsedURL 59 | return targetHost, nil 60 | } 61 | 62 | func urlParse(ref string) (*url.URL, error) { 63 | u, err := url.Parse(ref) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | if u.Host == "" { 69 | return nil, fmt.Errorf("host is empty") 70 | } 71 | 72 | if u.Scheme == "" { 73 | u.Scheme = "http" 74 | } 75 | 76 | return &url.URL{ 77 | Scheme: u.Scheme, 78 | Host: u.Host, 79 | }, nil 80 | } 81 | 82 | func (s *Session) NewRequest(method, uri string, body io.Reader) (*http.Request, error) { 83 | parsedURL, err := url.Parse(uri) 84 | 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if parsedURL.Scheme == "" { 90 | parsedURL.Scheme = targetHost.Scheme 91 | } 92 | 93 | if parsedURL.Host == "" { 94 | parsedURL.Host = targetHost.Host 95 | } 96 | 97 | req, err := http.NewRequest(method, parsedURL.String(), body) 98 | 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | return req, err 104 | } 105 | 106 | func escapeQuotes(s string) string { 107 | return strings.NewReplacer("\\", "\\\\", `"`, "\\\"").Replace(s) 108 | } 109 | 110 | func (s *Session) NewFileUploadRequest(uri string, params map[string]string, paramName string, asset *Asset) (*http.Request, error) { 111 | file, err := os.Open(asset.Path) 112 | if err != nil { 113 | return nil, err 114 | } 115 | defer file.Close() 116 | 117 | body := &bytes.Buffer{} 118 | writer := multipart.NewWriter(body) 119 | // part, err := writer.CreateFormFile(paramName, filepath.Base(path)) 120 | // Content-Typeを指定できないので該当コードから実装 121 | h := make(textproto.MIMEHeader) 122 | h.Set("Content-Disposition", 123 | fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 124 | escapeQuotes(paramName), escapeQuotes(filepath.Base(asset.Path)))) 125 | h.Set("Content-Type", asset.Type) 126 | part, err := writer.CreatePart(h) 127 | 128 | if err != nil { 129 | return nil, err 130 | } 131 | _, err = io.Copy(part, file) 132 | if err != nil { 133 | return nil, err 134 | } 135 | 136 | for key, val := range params { 137 | _ = writer.WriteField(key, val) 138 | } 139 | 140 | err = writer.Close() 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | parsedURL := &url.URL{ 146 | Scheme: targetHost.Scheme, 147 | Host: targetHost.Host, 148 | Path: uri, 149 | } 150 | 151 | req, err := http.NewRequest("POST", parsedURL.String(), body) 152 | if err == nil { 153 | req.Header.Add("Content-Type", writer.FormDataContentType()) 154 | } else { 155 | return nil, err 156 | } 157 | 158 | return req, err 159 | } 160 | 161 | func (s *Session) SendRequest(req *http.Request) (*http.Response, error) { 162 | req.Header.Set("User-Agent", UserAgent) 163 | 164 | return s.Client.Do(req) 165 | } 166 | 167 | func (s *Session) Success(point int64) { 168 | score.GetInstance().SetScore(point) 169 | } 170 | 171 | func (s *Session) Fail(point int64, req *http.Request, err error) error { 172 | score.GetInstance().SetFails(point) 173 | if req != nil { 174 | err = fmt.Errorf("%s (%s %s)", err, req.Method, req.URL.Path) 175 | } 176 | 177 | score.GetFailErrorsInstance().Append(err) 178 | return err 179 | } 180 | -------------------------------------------------------------------------------- /ansible_old/roles/portal/files/mysql/my.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # The MySQL database server configuration file. 3 | # 4 | # You can copy this to one of: 5 | # - "/etc/mysql/my.cnf" to set global options, 6 | # - "~/.my.cnf" to set user-specific options. 7 | # 8 | # One can use all long options that the program supports. 9 | # Run program with --help to get a list of available options and with 10 | # --print-defaults to see which it would actually understand and use. 11 | # 12 | # For explanations see 13 | # http://dev.mysql.com/doc/mysql/en/server-system-variables.html 14 | 15 | # This will be passed to all mysql clients 16 | # It has been reported that passwords should be enclosed with ticks/quotes 17 | # escpecially if they contain "#" chars... 18 | # Remember to edit /etc/mysql/debian.cnf when changing the socket location. 19 | [client] 20 | port = 3306 21 | socket = /var/run/mysqld/mysqld.sock 22 | 23 | # Here is entries for some specific programs 24 | # The following values assume you have at least 32M ram 25 | 26 | # This was formally known as [safe_mysqld]. Both versions are currently parsed. 27 | [mysqld_safe] 28 | socket = /var/run/mysqld/mysqld.sock 29 | nice = 0 30 | 31 | [mysqld] 32 | # 33 | # * Basic Settings 34 | # 35 | user = mysql 36 | pid-file = /var/run/mysqld/mysqld.pid 37 | socket = /var/run/mysqld/mysqld.sock 38 | port = 3306 39 | basedir = /usr 40 | datadir = /var/lib/mysql 41 | tmpdir = /tmp 42 | lc-messages-dir = /usr/share/mysql 43 | skip-external-locking 44 | # 45 | # Instead of skip-networking the default is now to listen only on 46 | # localhost which is more compatible and is not less secure. 47 | #bind-address = 127.0.0.1 48 | # 49 | # * Fine Tuning 50 | # 51 | key_buffer = 16M 52 | max_allowed_packet = 16M 53 | thread_stack = 192K 54 | thread_cache_size = 8 55 | # This replaces the startup script and checks MyISAM tables if needed 56 | # the first time they are touched 57 | myisam-recover = BACKUP 58 | #max_connections = 100 59 | #table_cache = 64 60 | #thread_concurrency = 10 61 | # 62 | # * Query Cache Configuration 63 | # 64 | query_cache_limit = 1M 65 | query_cache_size = 16M 66 | # 67 | # * Logging and Replication 68 | # 69 | # Both location gets rotated by the cronjob. 70 | # Be aware that this log type is a performance killer. 71 | # As of 5.1 you can enable the log at runtime! 72 | #general_log_file = /var/log/mysql/mysql.log 73 | #general_log = 1 74 | # 75 | # Error log - should be very few entries. 76 | # 77 | log_error = /var/log/mysql/error.log 78 | # 79 | # Here you can see queries with especially long duration 80 | #slow_query_log_file = /var/log/mysql/mysql-slow.log 81 | #slow_query_log = 1 82 | #long_query_time = 2 83 | #log_queries_not_using_indexes 84 | # 85 | # The following can be used as easy to replay backup logs or for replication. 86 | # note: if you are setting up a replication slave, see README.Debian about 87 | # other settings you may need to change. 88 | #server-id = 1 89 | #log_bin = /var/log/mysql/mysql-bin.log 90 | expire_logs_days = 10 91 | max_binlog_size = 100M 92 | #binlog_do_db = include_database_name 93 | #binlog_ignore_db = include_database_name 94 | # 95 | # * InnoDB 96 | # 97 | # InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/. 98 | # Read the manual for more InnoDB related options. There are many! 99 | # 100 | # * Security Features 101 | # 102 | # Read the manual, too, if you want chroot! 103 | # chroot = /var/lib/mysql/ 104 | # 105 | # For generating SSL certificates I recommend the OpenSSL GUI "tinyca". 106 | # 107 | # ssl-ca=/etc/mysql/cacert.pem 108 | # ssl-cert=/etc/mysql/server-cert.pem 109 | # ssl-key=/etc/mysql/server-key.pem 110 | 111 | character-set-server = utf8mb4 112 | 113 | 114 | 115 | [mysqldump] 116 | quick 117 | quote-names 118 | max_allowed_packet = 16M 119 | 120 | [mysql] 121 | #no-auto-rehash # faster start of mysql but no tab completition 122 | 123 | [isamchk] 124 | key_buffer = 16M 125 | 126 | # 127 | # * IMPORTANT: Additional settings that can override those from this file! 128 | # The files must end with '.cnf', otherwise they'll be ignored. 129 | # 130 | !includedir /etc/mysql/conf.d/ 131 | -------------------------------------------------------------------------------- /provisioning/image/ansible/04_xbuild.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: guests:extras 3 | become: yes 4 | become_user: isucon 5 | gather_facts: no 6 | tasks: 7 | - git: 8 | repo=https://github.com/tagomoris/xbuild.git 9 | dest=/home/isucon/.xbuild 10 | update=yes 11 | # ruby 12 | # datasource=ruby-version depName=ruby 13 | - command: /home/isucon/.xbuild/ruby-install 3.4.8 /home/isucon/.local/ruby 14 | args: 15 | creates: /home/isucon/.local/ruby/bin/ruby 16 | # node 17 | # datasource=node-version depName=node 18 | - command: /home/isucon/.xbuild/node-install v24.12.0 /home/isucon/.local/node 19 | args: 20 | creates: /home/isucon/.local/node/bin/node 21 | tags: 22 | - nodejs 23 | # datasource=python-version depName=python 24 | - command: /home/isucon/.xbuild/python-install 3.14.2 /home/isucon/.local/python 25 | args: 26 | creates: /home/isucon/.local/python/bin/python 27 | tags: 28 | - python3 29 | # golang 30 | # datasource=golang-version depName=golang 31 | - shell: /home/isucon/.xbuild/go-install 1.25.5 /home/isucon/.local/go $(uname -s | tr [A-Z] [a-z]) $(dpkg --print-architecture) 32 | args: 33 | creates: /home/isucon/.local/go/bin/go 34 | 35 | - hosts: guests:extras 36 | become: yes 37 | gather_facts: yes 38 | tasks: 39 | - name: Apt update 40 | apt: update_cache=yes 41 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "24.04" 42 | tags: php8 43 | - name: PHP8 install 44 | apt: name="{{item}}" 45 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "24.04" 46 | with_items: 47 | - php8.3-cli 48 | - php8.3-fpm 49 | - php8.3-mysql 50 | - php-memcached 51 | tags: php8 52 | - name: Update user and group in PHP-FPM config 53 | replace: 54 | path: /etc/php/8.3/fpm/pool.d/www.conf 55 | regexp: '^user = www-data' 56 | replace: 'user = isucon' 57 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "24.04" 58 | tags: php8 59 | - name: Update group in PHP-FPM config 60 | replace: 61 | path: /etc/php/8.3/fpm/pool.d/www.conf 62 | regexp: '^group = www-data' 63 | replace: 'group = isucon' 64 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "24.04" 65 | tags: php8 66 | - name: Comment out old listen directive in PHP-FPM config 67 | replace: 68 | path: /etc/php/8.3/fpm/pool.d/www.conf 69 | regexp: '^listen = /run/php/php8.3-fpm.sock' 70 | replace: ';listen = /run/php/php8.3-fpm.sock' 71 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "24.04" 72 | tags: php8 73 | - name: Add new listen directive in PHP-FPM config 74 | lineinfile: 75 | path: /etc/php/8.3/fpm/pool.d/www.conf 76 | insertafter: '^;listen = /run/php/php8.3-fpm.sock' 77 | line: 'listen = 127.0.0.1:9000' 78 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "24.04" 79 | tags: php8 80 | - name: Mod clear_env directive in PHP-FPM config 81 | lineinfile: 82 | path: /etc/php/8.3/fpm/pool.d/www.conf 83 | insertafter: '^;clear_env = no' 84 | line: 'clear_env = no' 85 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "24.04" 86 | tags: php8 87 | - name: stop php-fpm 88 | service: name=php8.3-fpm state=stopped enabled=no 89 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "24.04" 90 | tags: php8 91 | 92 | - hosts: guests:extras 93 | become: yes 94 | gather_facts: yes 95 | tasks: 96 | - name: Apt update 97 | apt: update_cache=yes 98 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "22.04" 99 | tags: php8 100 | - name: PHP8 install 101 | apt: name="{{item}}" 102 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "22.04" 103 | with_items: 104 | - php8.1-cli 105 | - php8.1-fpm 106 | - php8.1-mysql 107 | - php-memcached 108 | tags: php8 109 | - name: copy www.conf (php-fpm) 110 | copy: src=../files/etc/php/8.1/fpm/pool.d/www.conf dest=/etc/php/8.1/fpm/pool.d/www.conf owner=root mode=644 111 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "22.04" 112 | tags: php8 113 | - name: stop php-fpm 114 | service: name=php8.1-fpm state=stopped enabled=no 115 | when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "22.04" 116 | tags: php8 117 | --------------------------------------------------------------------------------