├── .github └── workflows │ ├── changelog.yml │ ├── gempush.yml │ ├── rspec.yml │ └── standard.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── config └── locales │ └── pig_ci │ └── en.yml ├── lib ├── pig_ci.rb └── pig_ci │ ├── configuration.rb │ ├── decorator.rb │ ├── decorator │ └── report_terminal_decorator.rb │ ├── metric.rb │ ├── metric │ ├── current.rb │ ├── historial │ │ └── change_percentage.rb │ └── historical.rb │ ├── profiler.rb │ ├── profiler │ ├── database_request.rb │ ├── memory.rb │ └── request_time.rb │ ├── profiler_engine.rb │ ├── profiler_engine │ └── rails.rb │ ├── report.rb │ ├── report │ ├── database_request.rb │ ├── memory.rb │ └── request_time.rb │ ├── summary.rb │ ├── summary │ ├── ci.rb │ ├── html.rb │ └── terminal.rb │ ├── test_frameworks.rb │ ├── test_frameworks │ └── rspec.rb │ ├── version.rb │ └── views │ ├── index.erb │ └── report.erb ├── pig_ci.gemspec ├── public └── assets │ ├── application.css │ └── application.js └── spec ├── fixtures └── files │ ├── memory.json │ ├── profiler-two-entries.json │ ├── profiler.json │ └── profiler.txt ├── lib ├── pig_ci │ ├── configuration │ │ └── thresholds_spec.rb │ ├── decorator │ │ └── report_terminal_decorator_spec.rb │ ├── decorator_spec.rb │ ├── metic │ │ ├── current_spec.rb │ │ ├── historical │ │ │ └── change_percentage_spec.rb │ │ └── historical_spec.rb │ ├── profiler │ │ ├── memory_spec.rb │ │ ├── request_time_spec.rb │ │ └── sql_active_record_spec.rb │ ├── profiler_engine │ │ └── rails_spec.rb │ ├── profiler_engine_spec.rb │ ├── profiler_spec.rb │ ├── report │ │ └── memory_spec.rb │ ├── report_spec.rb │ └── summary │ │ ├── ci_spec.rb │ │ ├── html_spec.rb │ │ └── terminal_spec.rb └── pig_ci_spec.rb ├── spec_helper.rb └── support ├── api └── schemas │ └── pigci.com │ └── v1 │ └── reports │ └── schema.json ├── api_schema_matcher.rb └── pig_ci_rails_application.rb /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | # From: https://github.com/hopsoft/stimulus_reflex/blob/master/.github/workflows/changelog.yml 2 | name: Changelog 3 | 4 | on: 5 | workflow_dispatch: 6 | release: 7 | types: [created] 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 4 16 | if: "!contains(github.event.head_commit.message, '[nodoc]')" 17 | steps: 18 | - uses: actions/checkout@master 19 | - name: Set up Ruby 3.0 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: 3.0 23 | - uses: actions/cache@v2 24 | with: 25 | path: vendor/bundle 26 | key: ${{ runner.os }}-changelog-gem-${{ hashFiles('**/Gemfile.lock') }} 27 | restore-keys: | 28 | ${{ runner.os }}-changelog-gem- 29 | - name: Create local changes 30 | run: | 31 | gem install github_changelog_generator -v "1.15.2" 32 | github_changelog_generator -u ${{ github.repository_owner }} -p ${{ github.event.repository.name }} --token ${{ secrets.GITHUB_TOKEN }} --exclude-labels duplicate,question,invalid,wontfix,nodoc 33 | - name: Commit files 34 | run: | 35 | git config --local user.email "github-actions@example.com" 36 | git config --local user.name "GitHub Actions" 37 | git commit -am "[nodoc] Update Changelog" || echo "No changes to commit" 38 | - name: Push changes 39 | uses: ad-m/github-push-action@master 40 | with: 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | branch: ${{ github.ref }} 43 | -------------------------------------------------------------------------------- /.github/workflows/gempush.yml: -------------------------------------------------------------------------------- 1 | name: Build & Publish Ruby Gem 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | name: Build + Publish 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Publish to GitHub Packages 15 | uses: jstastny/publish-gem-to-github@master 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | owner: MikeRogers0 19 | -------------------------------------------------------------------------------- /.github/workflows/rspec.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Testing 3 | 4 | on: 5 | push: 6 | pull_request: 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | ruby: [2.5, 2.6, 2.7] 14 | runs-on: ubuntu-latest 15 | name: Test against Ruby ${{ matrix.ruby }} 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: ${{ matrix.ruby }} 22 | bundler-cache: true 23 | - name: Run tests 24 | env: 25 | RAILS_ENV: test 26 | run: | 27 | bundle exec rake spec 28 | -------------------------------------------------------------------------------- /.github/workflows/standard.yml: -------------------------------------------------------------------------------- 1 | name: Standard 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - master 7 | jobs: 8 | standard: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Ruby 2.7 14 | uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: 2.7 17 | - name: Cache gems 18 | uses: actions/cache@v2 19 | with: 20 | path: vendor/bundle 21 | key: ${{ runner.os }}-standardrb 22 | - name: Install gems 23 | run: | 24 | bundle config path vendor/bundle 25 | gem install standardrb 26 | - name: Run Standard 27 | run: standardrb 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | 11 | *.gem 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v1.1.0](https://github.com/PigCI/pig-ci-rails/tree/v1.1.0) (2021-03-28) 4 | 5 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v1.0.1...v1.1.0) 6 | 7 | **Fixed bugs:** 8 | 9 | - Only eager loading app in Rails \>5.0 [\#38](https://github.com/PigCI/pig-ci-rails/pull/38) ([MikeRogers0](https://github.com/MikeRogers0)) 10 | 11 | **Merged pull requests:** 12 | 13 | - Adding End Of Life notice [\#68](https://github.com/PigCI/pig-ci-rails/pull/68) ([MikeRogers0](https://github.com/MikeRogers0)) 14 | - Updating .github/workflows to be simpler [\#67](https://github.com/PigCI/pig-ci-rails/pull/67) ([MikeRogers0](https://github.com/MikeRogers0)) 15 | - Using pigci.mikerogers.io over pigci.com [\#66](https://github.com/PigCI/pig-ci-rails/pull/66) ([MikeRogers0](https://github.com/MikeRogers0)) 16 | - Updating changelog generator to push to master [\#60](https://github.com/PigCI/pig-ci-rails/pull/60) ([MikeRogers0](https://github.com/MikeRogers0)) 17 | - Update webmock requirement from ~\> 3.9.3 to ~\> 3.11.0 [\#59](https://github.com/PigCI/pig-ci-rails/pull/59) ([dependabot[bot]](https://github.com/apps/dependabot)) 18 | - Upating Rubocop configuration [\#58](https://github.com/PigCI/pig-ci-rails/pull/58) ([MikeRogers0](https://github.com/MikeRogers0)) 19 | - Making GitHub Actions versions less specific [\#57](https://github.com/PigCI/pig-ci-rails/pull/57) ([MikeRogers0](https://github.com/MikeRogers0)) 20 | - Update simplecov requirement from ~\> 0.17.0 to ~\> 0.20.0 [\#56](https://github.com/PigCI/pig-ci-rails/pull/56) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 21 | - Update rspec requirement from ~\> 3.9.0 to ~\> 3.10.0 [\#53](https://github.com/PigCI/pig-ci-rails/pull/53) ([dependabot[bot]](https://github.com/apps/dependabot)) 22 | - Update terminal-table requirement from ~\> 1.8.0 to \>= 1.8, \< 2.1 [\#52](https://github.com/PigCI/pig-ci-rails/pull/52) ([dependabot[bot]](https://github.com/apps/dependabot)) 23 | - Revamping Github Actions [\#49](https://github.com/PigCI/pig-ci-rails/pull/49) ([MikeRogers0](https://github.com/MikeRogers0)) 24 | - Update actions/cache requirement to v2.1.2 [\#48](https://github.com/PigCI/pig-ci-rails/pull/48) ([dependabot[bot]](https://github.com/apps/dependabot)) 25 | - Update webmock requirement from ~\> 3.8.0 to ~\> 3.9.3 [\#47](https://github.com/PigCI/pig-ci-rails/pull/47) ([dependabot[bot]](https://github.com/apps/dependabot)) 26 | - Adding Dependabot.yml [\#45](https://github.com/PigCI/pig-ci-rails/pull/45) ([MikeRogers0](https://github.com/MikeRogers0)) 27 | - Ditching Docker Setup [\#44](https://github.com/PigCI/pig-ci-rails/pull/44) ([MikeRogers0](https://github.com/MikeRogers0)) 28 | - Adding funding\_uri metadata to gemspec [\#43](https://github.com/PigCI/pig-ci-rails/pull/43) ([MikeRogers0](https://github.com/MikeRogers0)) 29 | - Updating badges to reference GitHub Actions [\#42](https://github.com/PigCI/pig-ci-rails/pull/42) ([MikeRogers0](https://github.com/MikeRogers0)) 30 | - Removing Aptfile from repo [\#41](https://github.com/PigCI/pig-ci-rails/pull/41) ([MikeRogers0](https://github.com/MikeRogers0)) 31 | - Limiting GitHub Actions to just Rubocop & Rspec [\#40](https://github.com/PigCI/pig-ci-rails/pull/40) ([MikeRogers0](https://github.com/MikeRogers0)) 32 | - Updating docker setup to be a little slimmer [\#39](https://github.com/PigCI/pig-ci-rails/pull/39) ([MikeRogers0](https://github.com/MikeRogers0)) 33 | - Adding github sponsorship links [\#37](https://github.com/PigCI/pig-ci-rails/pull/37) ([MikeRogers0](https://github.com/MikeRogers0)) 34 | 35 | ## [v1.0.1](https://github.com/PigCI/pig-ci-rails/tree/v1.0.1) (2020-05-11) 36 | 37 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v1.0.0...v1.0.1) 38 | 39 | **Merged pull requests:** 40 | 41 | - Fixing spelling in the gemspec [\#36](https://github.com/PigCI/pig-ci-rails/pull/36) ([MikeRogers0](https://github.com/MikeRogers0)) 42 | - Adding autogenerated changelog [\#35](https://github.com/PigCI/pig-ci-rails/pull/35) ([MikeRogers0](https://github.com/MikeRogers0)) 43 | - Fixing missing logo in output summary [\#34](https://github.com/PigCI/pig-ci-rails/pull/34) ([MikeRogers0](https://github.com/MikeRogers0)) 44 | - Add option to ignore cached SQL queries [\#33](https://github.com/PigCI/pig-ci-rails/pull/33) ([MikeRogers0](https://github.com/MikeRogers0)) 45 | - Skip tracking on specific tests via RSpec metadata [\#32](https://github.com/PigCI/pig-ci-rails/pull/32) ([MikeRogers0](https://github.com/MikeRogers0)) 46 | - Adding in Docker [\#31](https://github.com/PigCI/pig-ci-rails/pull/31) ([MikeRogers0](https://github.com/MikeRogers0)) 47 | - Publishing Package to PigCI account [\#28](https://github.com/PigCI/pig-ci-rails/pull/28) ([MikeRogers0](https://github.com/MikeRogers0)) 48 | 49 | ## [v1.0.0](https://github.com/PigCI/pig-ci-rails/tree/v1.0.0) (2020-04-07) 50 | 51 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.2.2...v1.0.0) 52 | 53 | **Merged pull requests:** 54 | 55 | - Removing PigCI.com integration [\#27](https://github.com/PigCI/pig-ci-rails/pull/27) ([MikeRogers0](https://github.com/MikeRogers0)) 56 | 57 | ## [v0.2.2](https://github.com/PigCI/pig-ci-rails/tree/v0.2.2) (2020-03-23) 58 | 59 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.2.1...v0.2.2) 60 | 61 | **Merged pull requests:** 62 | 63 | - Fixing grammar on post\_install\_message [\#25](https://github.com/PigCI/pig-ci-rails/pull/25) ([MikeRogers0](https://github.com/MikeRogers0)) 64 | 65 | ## [v0.2.1](https://github.com/PigCI/pig-ci-rails/tree/v0.2.1) (2020-03-19) 66 | 67 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.2.0...v0.2.1) 68 | 69 | **Merged pull requests:** 70 | 71 | - Stop GitHub actions not to run on commit [\#24](https://github.com/PigCI/pig-ci-rails/pull/24) ([MikeRogers0](https://github.com/MikeRogers0)) 72 | - Handling JSON response for API key being incorrect with correct error [\#23](https://github.com/PigCI/pig-ci-rails/pull/23) ([MikeRogers0](https://github.com/MikeRogers0)) 73 | 74 | ## [v0.2.0](https://github.com/PigCI/pig-ci-rails/tree/v0.2.0) (2020-03-18) 75 | 76 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.5...v0.2.0) 77 | 78 | **Merged pull requests:** 79 | 80 | - Using GitHubs example for gempush.yml [\#22](https://github.com/PigCI/pig-ci-rails/pull/22) ([MikeRogers0](https://github.com/MikeRogers0)) 81 | - Replace references for limit to be threshold [\#21](https://github.com/PigCI/pig-ci-rails/pull/21) ([MikeRogers0](https://github.com/MikeRogers0)) 82 | - Setting up for 0.2.0 release [\#20](https://github.com/PigCI/pig-ci-rails/pull/20) ([MikeRogers0](https://github.com/MikeRogers0)) 83 | - Adding in action for when I tag a new version [\#19](https://github.com/PigCI/pig-ci-rails/pull/19) ([MikeRogers0](https://github.com/MikeRogers0)) 84 | - Adding config.thresholds, this allows standalone usage without pigci.com [\#18](https://github.com/PigCI/pig-ci-rails/pull/18) ([MikeRogers0](https://github.com/MikeRogers0)) 85 | - Update webmock requirement from ~\> 3.7.0 to ~\> 3.8.0 [\#17](https://github.com/PigCI/pig-ci-rails/pull/17) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 86 | - Update rspec requirement from ~\> 3.8.0 to ~\> 3.9.0 [\#16](https://github.com/PigCI/pig-ci-rails/pull/16) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 87 | - Updating TravisCI to test latest ruby versions [\#15](https://github.com/PigCI/pig-ci-rails/pull/15) ([MikeRogers0](https://github.com/MikeRogers0)) 88 | - Update rake requirement from ~\> 12.3 to ~\> 13.0 [\#14](https://github.com/PigCI/pig-ci-rails/pull/14) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 89 | 90 | ## [v0.1.5](https://github.com/PigCI/pig-ci-rails/tree/v0.1.5) (2019-09-23) 91 | 92 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.4...v0.1.5) 93 | 94 | **Merged pull requests:** 95 | 96 | - Widen i18n gem version requirement [\#12](https://github.com/PigCI/pig-ci-rails/pull/12) ([mileszim](https://github.com/mileszim)) 97 | 98 | ## [v0.1.4](https://github.com/PigCI/pig-ci-rails/tree/v0.1.4) (2019-09-10) 99 | 100 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.3...v0.1.4) 101 | 102 | **Implemented enhancements:** 103 | 104 | - Precompile assets before tests run [\#11](https://github.com/PigCI/pig-ci-rails/pull/11) ([MikeRogers0](https://github.com/MikeRogers0)) 105 | 106 | **Merged pull requests:** 107 | 108 | - Create FUNDING.yml [\#10](https://github.com/PigCI/pig-ci-rails/pull/10) ([MikeRogers0](https://github.com/MikeRogers0)) 109 | 110 | ## [v0.1.3](https://github.com/PigCI/pig-ci-rails/tree/v0.1.3) (2019-08-28) 111 | 112 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.2...v0.1.3) 113 | 114 | **Fixed bugs:** 115 | 116 | - Gracefully handling SockerError when submitting reports while offline [\#7](https://github.com/PigCI/pig-ci-rails/pull/7) ([MikeRogers0](https://github.com/MikeRogers0)) 117 | 118 | **Merged pull requests:** 119 | 120 | - Adding Dependabot Status badge to readme [\#9](https://github.com/PigCI/pig-ci-rails/pull/9) ([MikeRogers0](https://github.com/MikeRogers0)) 121 | - Fixing duplicate word typo in readme [\#8](https://github.com/PigCI/pig-ci-rails/pull/8) ([MikeRogers0](https://github.com/MikeRogers0)) 122 | - Update webmock requirement from ~\> 3.6.0 to ~\> 3.7.0 [\#6](https://github.com/PigCI/pig-ci-rails/pull/6) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 123 | 124 | ## [v0.1.2](https://github.com/PigCI/pig-ci-rails/tree/v0.1.2) (2019-08-03) 125 | 126 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.1...v0.1.2) 127 | 128 | **Implemented enhancements:** 129 | 130 | - Add options to control eager loading of rails app [\#3](https://github.com/PigCI/pig-ci-rails/pull/3) ([MikeRogers0](https://github.com/MikeRogers0)) 131 | 132 | **Fixed bugs:** 133 | 134 | - Fixing issue where timezone would becomes the machines on app warmup [\#5](https://github.com/PigCI/pig-ci-rails/pull/5) ([MikeRogers0](https://github.com/MikeRogers0)) 135 | 136 | **Merged pull requests:** 137 | 138 | - Fixing up some codacy issues [\#4](https://github.com/PigCI/pig-ci-rails/pull/4) ([MikeRogers0](https://github.com/MikeRogers0)) 139 | 140 | ## [v0.1.1](https://github.com/PigCI/pig-ci-rails/tree/v0.1.1) (2019-07-31) 141 | 142 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/v0.1.0...v0.1.1) 143 | 144 | **Merged pull requests:** 145 | 146 | - Adding fallback for if PigCI is have API issues [\#2](https://github.com/PigCI/pig-ci-rails/pull/2) ([MikeRogers0](https://github.com/MikeRogers0)) 147 | - Adding test coverage with codeclimate [\#1](https://github.com/PigCI/pig-ci-rails/pull/1) ([MikeRogers0](https://github.com/MikeRogers0)) 148 | 149 | ## [v0.1.0](https://github.com/PigCI/pig-ci-rails/tree/v0.1.0) (2019-07-30) 150 | 151 | [Full Changelog](https://github.com/PigCI/pig-ci-rails/compare/26699470a370a1ff576f820a0742be5f332561be...v0.1.0) 152 | 153 | 154 | 155 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 156 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at me@mikerogers.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in pig_ci.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mike Rogers 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
6 | Monitor your Ruby Applications metrics (Memory, SQL Requests & Request Time) as part of your test suite. If your app exceeds an acceptable threshold it'll fail the test suite. 7 |
8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Generated by PigCI.
11 |Support PigCI by buying me a coffee.
12 | 13 | report: 14 | memory: 15 | name: Peak memory per a request 16 | attributes: 17 | key: Key 18 | max: Max (MB) 19 | min: Min (MB) 20 | mean: Mean (MB) 21 | number_of_requests: Requests 22 | max_change_percentage: "% Change" 23 | 24 | request_time: 25 | name: Request Time 26 | attributes: 27 | key: Key 28 | max: Max (ms) 29 | min: Min (ms) 30 | mean: Mean (ms) 31 | number_of_requests: Requests 32 | max_change_percentage: "% Change" 33 | 34 | database_request: 35 | name: Queries to the database 36 | attributes: 37 | key: Key 38 | max: Max 39 | min: Min 40 | mean: Mean 41 | number_of_requests: Requests 42 | max_change_percentage: "% Change" 43 | -------------------------------------------------------------------------------- /lib/pig_ci.rb: -------------------------------------------------------------------------------- 1 | require "active_support" 2 | require "active_support/core_ext/string/inflections" 3 | require "rake" 4 | 5 | require "pig_ci/version" 6 | require "pig_ci/configuration" 7 | require "pig_ci/decorator" 8 | require "pig_ci/summary" 9 | require "pig_ci/profiler_engine" 10 | require "pig_ci/profiler" 11 | require "pig_ci/metric" 12 | require "pig_ci/report" 13 | require "pig_ci/test_frameworks" 14 | 15 | module PigCI 16 | extend self 17 | 18 | attr_accessor :pid 19 | 20 | attr_writer :enabled 21 | def enabled? 22 | @enabled.nil? ? true : @enabled 23 | end 24 | 25 | # Rails caches repeated queries within the same request. You can not count 26 | # any cached queries if you'd like. 27 | attr_writer :ignore_cached_queries 28 | def ignore_cached_queries? 29 | @ignore_cached_queries.nil? ? false : @ignore_cached_queries 30 | end 31 | 32 | attr_writer :tmp_directory 33 | def tmp_directory 34 | @tmp_directory || Pathname.new(Dir.getwd).join("tmp", "pig-ci") 35 | end 36 | 37 | attr_writer :output_directory 38 | def output_directory 39 | @output_directory || Pathname.new(Dir.getwd).join("pig-ci") 40 | end 41 | 42 | attr_accessor :generate_terminal_summary 43 | def generate_terminal_summary? 44 | @generate_terminal_summary.nil? || @generate_terminal_summary 45 | end 46 | 47 | attr_accessor :generate_html_summary 48 | def generate_html_summary? 49 | @generate_html_summary.nil? || @generate_html_summary 50 | end 51 | 52 | attr_writer :max_change_percentage_precision 53 | def max_change_percentage_precision 54 | @max_change_percentage_precision || 1 55 | end 56 | 57 | attr_writer :report_memory_precision 58 | def report_memory_precision 59 | @report_memory_precision || 2 60 | end 61 | 62 | attr_writer :during_setup_eager_load_application 63 | def during_setup_eager_load_application? 64 | @during_setup_eager_load_application.nil? || @during_setup_eager_load_application 65 | end 66 | 67 | attr_writer :during_setup_make_blank_application_request 68 | def during_setup_make_blank_application_request? 69 | @during_setup_make_blank_application_request.nil? || @during_setup_make_blank_application_request 70 | end 71 | 72 | attr_writer :during_setup_precompile_assets 73 | def during_setup_precompile_assets? 74 | @during_setup_precompile_assets.nil? || @during_setup_precompile_assets 75 | end 76 | 77 | attr_writer :terminal_report_row_limit 78 | def terminal_report_row_limit 79 | @terminal_report_row_limit || -1 80 | end 81 | 82 | # PigCI.report_row_sort_by = Proc.new { |d| d[:max_change_percentage] * -1 } 83 | attr_writer :report_row_sort_by 84 | def report_row_sort_by(data) 85 | (@report_row_sort_by || proc { |d| d[:max].to_i * -1 }).call(data) 86 | end 87 | 88 | attr_writer :historical_data_run_limit 89 | def historical_data_run_limit 90 | @historical_data_run_limit ||= 10 91 | end 92 | 93 | attr_writer :run_timestamp 94 | def run_timestamp 95 | @run_timestamp ||= Time.now.to_i.to_s 96 | end 97 | 98 | attr_writer :profiler_engine 99 | def profiler_engine 100 | @profiler_engine ||= PigCI::ProfilerEngine::Rails.new 101 | end 102 | 103 | attr_writer :commit_sha1 104 | def commit_sha1 105 | @commit_sha1 || ENV["CI_COMMIT_ID"] || ENV["CIRCLE_SHA1"] || ENV["TRAVIS_COMMIT"] || `git rev-parse HEAD`.strip 106 | end 107 | 108 | attr_writer :head_branch 109 | def head_branch 110 | @head_branch || ENV["CI_BRANCH"] || ENV["CIRCLE_BRANCH"] || ENV["TRAVIS_BRANCH"] || `git rev-parse --abbrev-ref HEAD`.strip 111 | end 112 | 113 | # Throw deprecation notice for setting API 114 | def api_key=(value) 115 | puts "DEPRECATED: PigCI.com API has been retired, you no longer need to set config.api_key in your spec/rails_helper.rb file." 116 | end 117 | 118 | attr_writer :locale 119 | def locale 120 | @locale || :en 121 | end 122 | 123 | def thresholds=(values) 124 | @thresholds = PigCI::Configuration::Thresholds.new(values) 125 | end 126 | 127 | def thresholds 128 | @thresholds ||= PigCI::Configuration::Thresholds.new 129 | end 130 | 131 | module_function 132 | 133 | def start(&block) 134 | self.pid = Process.pid 135 | PigCI::TestFrameworks::Rspec.configure! if defined?(::RSpec) 136 | 137 | block&.call(self) 138 | 139 | # Add our translations 140 | load_i18ns! 141 | 142 | # Make sure our directories exist 143 | Dir.mkdir(tmp_directory) unless File.exist?(tmp_directory) 144 | Dir.mkdir(output_directory) unless File.exist?(output_directory) 145 | 146 | # Purge any previous logs and attach some listeners 147 | profiler_engine.setup! 148 | end 149 | 150 | def load_i18ns! 151 | I18n.available_locales << PigCI.locale 152 | I18n.load_path += Dir["#{File.expand_path("../config/locales/pig_ci", __dir__)}/*.{rb,yml}"] 153 | end 154 | 155 | def run_exit_tasks! 156 | return if PigCI.pid != Process.pid || !PigCI.profiler_engine.request_captured? 157 | 158 | # Save all the reports as JSON 159 | profiler_engine.profilers.each(&:save!) 160 | 161 | # Print the report summary to Terminal 162 | PigCI::Summary::Terminal.new(reports: profiler_engine.reports).print! if PigCI.generate_terminal_summary? 163 | 164 | # Save the report summary to the project root. 165 | PigCI::Summary::HTML.new(reports: profiler_engine.reports).save! if PigCI.generate_html_summary? 166 | 167 | # Make sure CI fails when metrics are over thresholds. 168 | PigCI::Summary::CI.new(reports: profiler_engine.reports).call! 169 | end 170 | end 171 | 172 | at_exit do 173 | PigCI.run_exit_tasks! 174 | end 175 | -------------------------------------------------------------------------------- /lib/pig_ci/configuration.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Configuration 2 | Thresholds = Struct.new(:memory, :request_time, :database_request) { 3 | def initialize(memory: 350, request_time: 250, database_request: 35) 4 | super(memory, request_time, database_request) 5 | end 6 | } 7 | end 8 | -------------------------------------------------------------------------------- /lib/pig_ci/decorator.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Decorator 2 | attr_accessor :object 3 | 4 | def initialize(object) 5 | @object = object 6 | end 7 | end 8 | 9 | require "pig_ci/decorator/report_terminal_decorator" 10 | -------------------------------------------------------------------------------- /lib/pig_ci/decorator/report_terminal_decorator.rb: -------------------------------------------------------------------------------- 1 | require "colorized_string" 2 | 3 | class PigCI::Decorator::ReportTerminalDecorator < PigCI::Decorator 4 | %i[key max min mean number_of_requests].each do |field| 5 | define_method(field) do 6 | @object[field] 7 | end 8 | end 9 | 10 | def max_change_percentage 11 | if @object[:max_change_percentage].start_with?("-") 12 | ColorizedString[@object[:max_change_percentage]].colorize(:green) 13 | elsif @object[:max_change_percentage].start_with?("0.0") 14 | @object[:max_change_percentage] 15 | else 16 | ColorizedString[@object[:max_change_percentage]].colorize(:red) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pig_ci/metric.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Metric; end 2 | 3 | require "pig_ci/metric/current" 4 | require "pig_ci/metric/historical" 5 | -------------------------------------------------------------------------------- /lib/pig_ci/metric/current.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Metric::Current 2 | def initialize(log_file:) 3 | @log_file = log_file 4 | end 5 | 6 | def to_h 7 | @to_h = {} 8 | 9 | File.foreach(@log_file) do |f| 10 | key, value = f.strip.split("|") 11 | value = value.to_i 12 | 13 | @to_h[key] ||= { 14 | key: key, 15 | max: value, 16 | min: value, 17 | mean: 0, 18 | total: 0, 19 | number_of_requests: 0 20 | } 21 | @to_h[key][:max] = value if value > @to_h[key][:max] 22 | @to_h[key][:min] = value if value < @to_h[key][:min] 23 | @to_h[key][:total] += value 24 | @to_h[key][:number_of_requests] += 1 25 | @to_h[key][:mean] = @to_h[key][:total] / @to_h[key][:number_of_requests] 26 | end 27 | 28 | @to_h.collect { |_k, d| d } 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pig_ci/metric/historial/change_percentage.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Metric::Historical::ChangePercentage 2 | def initialize(previous_data:, data:) 3 | @previous_data = previous_data 4 | @data = data 5 | @timestamp = @data.keys.first 6 | @profiler = @data[@timestamp].keys.first 7 | end 8 | 9 | def updated_data 10 | @data[@timestamp][@profiler].collect do |data| 11 | previous_run_data = previous_run_data_for_key(data[:key]) || data 12 | 13 | data[:max_change_percentage] = (((BigDecimal(data[:max]) - BigDecimal(previous_run_data[:max])) / BigDecimal(previous_run_data[:max])) * 100).round(PigCI.max_change_percentage_precision) 14 | data[:max_change_percentage] = BigDecimal("0") if data[:max_change_percentage].to_s == "NaN" || data[:max_change_percentage] == BigDecimal("-0.0") 15 | data[:max_change_percentage] = data[:max_change_percentage].to_f 16 | 17 | data 18 | end 19 | 20 | @data 21 | end 22 | 23 | private 24 | 25 | def previous_run_data_for_key(key) 26 | previous_data_keys.each do |previous_run_key| 27 | @previous_data[previous_run_key][@profiler.to_sym].each do |raw_previous_run_data| 28 | return raw_previous_run_data if raw_previous_run_data[:key] == key 29 | end 30 | end 31 | nil 32 | end 33 | 34 | def previous_data_keys 35 | @previous_data_keys ||= @previous_data.keys.sort.reverse 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/pig_ci/metric/historical.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Metric::Historical 2 | def initialize(historical_log_file:) 3 | @historical_log_file = historical_log_file 4 | end 5 | 6 | def to_h 7 | @to_h ||= read_historical_log_file.sort_by { |timestamp, _data| timestamp.to_s.to_i * -1 }.to_h 8 | end 9 | 10 | # In future this might honour some limit. 11 | def append!(timestamp:, metric:, data:) 12 | to_h 13 | @to_h[timestamp] ||= {} 14 | @to_h[timestamp][metric] = data 15 | remove_old_historical_data! 16 | save! 17 | end 18 | 19 | def add_change_percentage_and_append!(timestamp:, metric:, data:) 20 | max_change_percentage_data = {} 21 | max_change_percentage_data[timestamp] = {} 22 | max_change_percentage_data[timestamp][metric] = data 23 | 24 | data = PigCI::Metric::Historical::ChangePercentage.new(previous_data: to_h, data: max_change_percentage_data).updated_data 25 | append!(timestamp: timestamp, metric: metric, data: data[timestamp][metric]) 26 | end 27 | 28 | private 29 | 30 | def remove_old_historical_data! 31 | new_historical_data = @to_h 32 | .sort_by { |timestamp, _data| timestamp.to_s.to_i * -1 }[0..(PigCI.historical_data_run_limit - 1)] 33 | .to_h 34 | .sort_by { |timestamp, _data| timestamp.to_s.to_i * -1 }.to_h 35 | @to_h = new_historical_data 36 | end 37 | 38 | def read_historical_log_file 39 | if File.exist?(@historical_log_file) 40 | JSON.parse(File.open(@historical_log_file, "r").read, symbolize_names: true) 41 | else 42 | {} 43 | end 44 | end 45 | 46 | def save! 47 | File.write(@historical_log_file, @to_h.to_json) 48 | @to_h = nil 49 | end 50 | end 51 | 52 | require "pig_ci/metric/historial/change_percentage" 53 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Profiler 2 | attr_accessor :log_value, :log_file, :historical_log_file, :i18n_key 3 | 4 | def initialize(i18n_key: nil, log_file: nil, historical_log_file: nil) 5 | @i18n_key = i18n_key || self.class.name.underscore.split("/").last 6 | @log_file = log_file || PigCI.tmp_directory.join("#{@i18n_key}.txt") 7 | @historical_log_file = historical_log_file || PigCI.tmp_directory.join("#{@i18n_key}.json") 8 | @log_value = 0 9 | end 10 | 11 | def setup! 12 | File.open(log_file, "w") { |file| file.truncate(0) } 13 | end 14 | 15 | def reset! 16 | @log_value = 0 17 | end 18 | 19 | def log_request!(request_key) 20 | File.open(log_file, "a+") do |f| 21 | f.puts([request_key, log_value].join("|")) 22 | end 23 | end 24 | 25 | def save! 26 | historical_data = PigCI::Metric::Historical.new(historical_log_file: @historical_log_file) 27 | historical_data.add_change_percentage_and_append!( 28 | timestamp: PigCI.run_timestamp, 29 | metric: i18n_key, 30 | data: PigCI::Metric::Current.new(log_file: log_file).to_h 31 | ) 32 | end 33 | 34 | def increment!(*) 35 | raise NotImplementedError 36 | end 37 | end 38 | 39 | require "pig_ci/profiler/memory" 40 | require "pig_ci/profiler/request_time" 41 | require "pig_ci/profiler/database_request" 42 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler/database_request.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Profiler::DatabaseRequest < PigCI::Profiler 2 | def increment!(by: 1) 3 | @log_value += by 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler/memory.rb: -------------------------------------------------------------------------------- 1 | require "get_process_mem" 2 | 3 | class PigCI::Profiler::Memory < PigCI::Profiler 4 | def reset! 5 | GC.disable 6 | end 7 | 8 | def log_request!(request_key) 9 | GC.enable 10 | super 11 | end 12 | 13 | def log_value 14 | ::GetProcessMem.new.bytes 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler/request_time.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Profiler::RequestTime < PigCI::Profiler 2 | attr_accessor :start_time, :end_time 3 | 4 | def reset! 5 | super 6 | @start_time = Time.now.utc 7 | end 8 | 9 | def log_request!(request_key) 10 | @end_time = Time.now.utc 11 | super 12 | end 13 | 14 | def log_value 15 | (@end_time - @start_time) * 1000.0 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler_engine.rb: -------------------------------------------------------------------------------- 1 | class PigCI::ProfilerEngine 2 | attr_accessor :request_key, :profilers, :reports, :request_captured 3 | 4 | def initialize(profilers: nil, reports: nil) 5 | @profilers = profilers || [] 6 | @reports = reports || [] 7 | @request_captured = false 8 | end 9 | 10 | def request_key? 11 | !@request_key.nil? && @request_key != "" 12 | end 13 | 14 | def request_captured? 15 | @request_captured 16 | end 17 | 18 | def request_captured! 19 | @request_captured = true 20 | end 21 | 22 | def setup! 23 | Dir.mkdir(PigCI.tmp_directory) unless File.exist?(PigCI.tmp_directory) 24 | 25 | yield if block_given? 26 | 27 | profilers.collect(&:setup!) 28 | 29 | # Attach listeners to the rails events. 30 | attach_listeners! 31 | end 32 | 33 | private 34 | 35 | def attach_listeners! 36 | raise NotImplementedError 37 | end 38 | end 39 | 40 | require "pig_ci/profiler_engine/rails" 41 | -------------------------------------------------------------------------------- /lib/pig_ci/profiler_engine/rails.rb: -------------------------------------------------------------------------------- 1 | # This subscribes to the ActiveSupport::Notifications and passes it onto our profilers. 2 | class PigCI::ProfilerEngine::Rails < ::PigCI::ProfilerEngine 3 | def initialize(profilers: nil, reports: nil) 4 | @profilers = profilers || [ 5 | PigCI::Profiler::Memory.new, 6 | PigCI::Profiler::RequestTime.new, 7 | PigCI::Profiler::DatabaseRequest.new 8 | ] 9 | @reports = reports || [ 10 | PigCI::Report::Memory.new, 11 | PigCI::Report::RequestTime.new, 12 | PigCI::Report::DatabaseRequest.new 13 | ] 14 | @request_captured = false 15 | end 16 | 17 | def request_key_from_payload!(payload) 18 | @request_key = "#{payload[:method]} #{payload[:controller]}##{payload[:action]}{format:#{payload[:format]}}" 19 | end 20 | 21 | def setup! 22 | super do 23 | precompile_assets! if PigCI.during_setup_precompile_assets? 24 | eager_load_rails! if PigCI.during_setup_eager_load_application? 25 | make_blank_application_request! if PigCI.during_setup_make_blank_application_request? 26 | end 27 | end 28 | 29 | private 30 | 31 | def precompile_assets! 32 | # From: https://github.com/rails/sprockets-rails/blob/e9ca63edb6e658cdfcf8a35670c525b369c2ccca/test/test_railtie.rb#L7-L13 33 | ::Rails.application.load_tasks 34 | ::Rake.application["assets:precompile"].execute 35 | end 36 | 37 | def eager_load_rails! 38 | # None of these methods will work pre-rails 5. 39 | return unless ::Rails.version.to_f >= 5.0 40 | 41 | # Eager load rails to give more accurate memory levels. 42 | ::Rails.application.eager_load! 43 | ::Rails.application.routes.eager_load! 44 | ::Rails::Engine.subclasses.map(&:instance).each(&:eager_load!) 45 | ::ActiveRecord::Base.descendants 46 | end 47 | 48 | def make_blank_application_request! 49 | # Make a call to the root path to load up as much of rails as possible 50 | # Done within a timezone block as it affects the timezone. 51 | Time.use_zone("UTC") do 52 | ::Rails.application.call(::Rack::MockRequest.env_for("/")) 53 | end 54 | end 55 | 56 | def attach_listeners! 57 | ::ActiveSupport::Notifications.subscribe "start_processing.action_controller" do |_name, _started, _finished, _unique_id, payload| 58 | request_key_from_payload!(payload) 59 | 60 | profilers.each(&:reset!) 61 | end 62 | 63 | ::ActiveSupport::Notifications.subscribe "sql.active_record" do |_name, _started, _finished, _unique_id, payload| 64 | if request_key? && PigCI.enabled? && (!PigCI.ignore_cached_queries? || (PigCI.ignore_cached_queries? && !payload[:cached])) 65 | profilers.select { |profiler| profiler.instance_of?(PigCI::Profiler::DatabaseRequest) }.each(&:increment!) 66 | end 67 | end 68 | 69 | ::ActiveSupport::Notifications.subscribe "process_action.action_controller" do |_name, _started, _finished, _unique_id, _payload| 70 | if PigCI.enabled? 71 | profilers.each do |profiler| 72 | profiler.log_request!(request_key) 73 | end 74 | 75 | request_captured! 76 | end 77 | self.request_key = nil 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/pig_ci/report.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Report 2 | attr_accessor :historical_log_file, :i18n_key 3 | 4 | def initialize(historical_log_file: nil, i18n_key: nil, timestamp: nil) 5 | @i18n_key = i18n_key || self.class.name.underscore.split("/").last 6 | @historical_log_file = historical_log_file || PigCI.tmp_directory.join("#{@i18n_key}.json") 7 | @timestamp = timestamp || PigCI.run_timestamp 8 | end 9 | 10 | def headings 11 | column_keys.collect { |key| I18n.t(".attributes.#{key}", scope: i18n_scope, locale: PigCI.locale) } 12 | end 13 | 14 | def i18n_name 15 | I18n.t(".name", scope: i18n_scope, locale: PigCI.locale) 16 | end 17 | 18 | def max_for(timestamp) 19 | sorted_and_formatted_data_for(timestamp).collect { |row| row[:max] }.max 20 | end 21 | 22 | def threshold 23 | PigCI.thresholds.dig(@i18n_key.to_sym) 24 | end 25 | 26 | def over_threshold_for?(timestamp) 27 | return false unless threshold.present? && max_for(timestamp).present? 28 | 29 | max_for(timestamp) > threshold 30 | end 31 | 32 | def sorted_and_formatted_data_for(timestamp) 33 | data_for(timestamp)[@i18n_key.to_sym].sort_by { |data| 34 | PigCI.report_row_sort_by(data) 35 | }.collect do |data| 36 | self.class.format_row(data) 37 | end 38 | end 39 | 40 | def to_payload_for(timestamp) 41 | { 42 | profiler: @i18n_key.to_sym, 43 | data: data_for(timestamp)[@i18n_key.to_sym] 44 | } 45 | end 46 | 47 | def historical_data 48 | @historical_data ||= PigCI::Metric::Historical.new(historical_log_file: @historical_log_file).to_h 49 | end 50 | 51 | def timestamps 52 | historical_data.keys 53 | end 54 | 55 | def column_keys 56 | %i[key max min mean number_of_requests max_change_percentage] 57 | end 58 | 59 | def i18n_scope 60 | @i18n_scope ||= "pig_ci.report.#{i18n_key}" 61 | end 62 | 63 | def self.format_row(row) 64 | row = row.dup 65 | row[:max_change_percentage] = "#{row[:max_change_percentage]}%" 66 | row 67 | end 68 | 69 | private 70 | 71 | def data_for(timestamp) 72 | historical_data[timestamp.to_sym] 73 | end 74 | end 75 | 76 | require "pig_ci/report/memory" 77 | require "pig_ci/report/request_time" 78 | require "pig_ci/report/database_request" 79 | -------------------------------------------------------------------------------- /lib/pig_ci/report/database_request.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Report::DatabaseRequest < PigCI::Report 2 | end 3 | -------------------------------------------------------------------------------- /lib/pig_ci/report/memory.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Report::Memory < PigCI::Report 2 | def self.format_row(_unformatted_row) 3 | row = super 4 | row[:max] = (row[:max] / bytes_in_a_megabyte).round(PigCI.report_memory_precision) 5 | row[:min] = (row[:min] / bytes_in_a_megabyte).round(PigCI.report_memory_precision) 6 | row[:mean] = (row[:mean] / bytes_in_a_megabyte).round(PigCI.report_memory_precision) 7 | row[:total] = (row[:total] / bytes_in_a_megabyte).round(PigCI.report_memory_precision) 8 | 9 | row 10 | end 11 | 12 | def self.bytes_in_a_megabyte 13 | @bytes_in_a_megabyte ||= BigDecimal("1_048_576") 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/pig_ci/report/request_time.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Report::RequestTime < PigCI::Report 2 | end 3 | -------------------------------------------------------------------------------- /lib/pig_ci/summary.rb: -------------------------------------------------------------------------------- 1 | class PigCI::Summary; end 2 | 3 | require "pig_ci/summary/ci" 4 | require "pig_ci/summary/html" 5 | require "pig_ci/summary/terminal" 6 | -------------------------------------------------------------------------------- /lib/pig_ci/summary/ci.rb: -------------------------------------------------------------------------------- 1 | require "colorized_string" 2 | 3 | class PigCI::Summary::CI < PigCI::Summary 4 | def initialize(reports:) 5 | @reports = reports 6 | @timestamp = PigCI.run_timestamp 7 | end 8 | 9 | def call! 10 | puts "" 11 | puts I18n.t("pig_ci.summary.ci_start") 12 | 13 | over_threshold = false 14 | @reports.each do |report| 15 | print_report(report) 16 | over_threshold = true if report.over_threshold_for?(@timestamp) 17 | end 18 | 19 | fail_with_error! if over_threshold 20 | puts "" 21 | end 22 | 23 | private 24 | 25 | def fail_with_error! 26 | puts I18n.t("pig_ci.summary.ci_failure") 27 | Kernel.exit(2) 28 | end 29 | 30 | def print_report(report) 31 | max_and_threshold = [ 32 | report.max_for(@timestamp).to_s, 33 | "/", 34 | report.threshold 35 | ].join(" ") 36 | 37 | if report.over_threshold_for?(@timestamp) 38 | puts "#{report.i18n_name}: #{ColorizedString[max_and_threshold].colorize(:red)}\n" 39 | else 40 | puts "#{report.i18n_name}: #{ColorizedString[max_and_threshold].colorize(:green)}\n" 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/pig_ci/summary/html.rb: -------------------------------------------------------------------------------- 1 | # This will make human friendly HTML report for developers to compare runs locally. 2 | # It aims to save to the project root in /pig-ci. 3 | # It is inspired by https://github.com/colszowka/simplecov-html 4 | 5 | require "erb" 6 | 7 | class PigCI::Summary::HTML < PigCI::Summary 8 | def initialize(reports:) 9 | @reports = reports 10 | end 11 | 12 | def save! 13 | copy_assets! 14 | File.write(index_file_path, template("index").result(binding)) 15 | 16 | puts I18n.t("pig_ci.summary.saved_successfully", output_directory: PigCI.output_directory) 17 | end 18 | 19 | private 20 | 21 | def render_report(report) 22 | template("report").result(binding) 23 | end 24 | 25 | def timestamps 26 | @reports.first.timestamps 27 | end 28 | 29 | def template(name) 30 | ERB.new(File.read(File.join(File.dirname(__FILE__), "../views", "#{name}.erb"))) 31 | end 32 | 33 | def index_file_path 34 | PigCI.output_directory.join("index.html") 35 | end 36 | 37 | def copy_assets! 38 | Dir.mkdir(output_assets_directory) unless File.exist?(output_assets_directory) 39 | FileUtils.copy_entry(assets_directory, output_assets_directory) 40 | end 41 | 42 | def output_assets_directory 43 | PigCI.output_directory.join("assets") 44 | end 45 | 46 | def assets_directory 47 | File.join(File.dirname(__FILE__), "../../../public/assets") 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/pig_ci/summary/terminal.rb: -------------------------------------------------------------------------------- 1 | require "terminal-table" 2 | 3 | class PigCI::Summary::Terminal < PigCI::Summary 4 | def initialize(reports:) 5 | @reports = reports 6 | @timestamp = PigCI.run_timestamp 7 | end 8 | 9 | def print! 10 | @reports.each do |report| 11 | print_report(report) 12 | end 13 | end 14 | 15 | private 16 | 17 | def print_report(report) 18 | puts "#{report.i18n_name}:\n" 19 | 20 | table = ::Terminal::Table.new headings: report.headings do |t| 21 | report.sorted_and_formatted_data_for(@timestamp)[0..PigCI.terminal_report_row_limit] 22 | .collect { |data| PigCI::Decorator::ReportTerminalDecorator.new(data) } 23 | .each do |data| 24 | t << report.column_keys.collect { |key| data.send(key) } 25 | end 26 | end 27 | puts table 28 | puts "\n" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pig_ci/test_frameworks.rb: -------------------------------------------------------------------------------- 1 | module PigCI::TestFrameworks; end 2 | 3 | require "pig_ci/test_frameworks/rspec" 4 | -------------------------------------------------------------------------------- /lib/pig_ci/test_frameworks/rspec.rb: -------------------------------------------------------------------------------- 1 | class PigCI::TestFrameworks::Rspec 2 | def self.configure! 3 | if defined?(::RSpec) 4 | ::RSpec.configure do |config| 5 | config.around(:each, pig_ci: false) do |example| 6 | @pig_ci_enabled = PigCI.enabled? 7 | PigCI.enabled = false 8 | example.run 9 | PigCI.enabled = @pig_ci_enabled 10 | end 11 | 12 | config.around(:each, pig_ci: true) do |example| 13 | @pig_ci_enabled = PigCI.enabled? 14 | PigCI.enabled = true 15 | example.run 16 | PigCI.enabled = @pig_ci_enabled 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pig_ci/version.rb: -------------------------------------------------------------------------------- 1 | module PigCI 2 | VERSION = "1.1.0".freeze 3 | end 4 | -------------------------------------------------------------------------------- /lib/pig_ci/views/index.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |