CircleCI

pg-eyeballs 👀

pg-eyeballs is a ruby gem that gives you detailed information about how the SQL queries created by the active record code you write are executed by the database. It gives you an easy, ruby friendly way to see the output of the Postgres EXPLAIN command and integrates with the popular query analysis tool gocmdpev.

Using it you can see:

  • What queries were run
  • How long they took
  • Which indexes were used
  • Which algorithms were used
  • Much more!

Installation

Add this line to your application's Gemfile:

gem 'pg-eyeballs'

And then execute:

$ bundle

Or install it yourself as:

$ gem install pg-eyeballs

Usage

explain(options: [:analyze, :verbose, :costs, :buffers], format: :text)

User.all.eyeballs.explain

["Seq Scan on public.users  (cost=0.00..22.30 rows=1230 width=36) (actual time=0.002..0.002 rows=1 loops=1)
    Output: id, email
    Buffers: shared hit=1
  Planning time: 0.014 ms
  Execution time: 0.009 ms"
]

Most eyeballs methods return an array because an ActiveRecord::Relation can run more than one query, for instance when it has a preload or with certain subqueries

User.all.preload(:profiles).eyeballs.explain(options: [:verbose], format: :yaml)
['- Plan:
      Node Type: "Seq Scan"
      Relation Name: "users"
      Schema: "public"
      Alias: "users"
      Startup Cost: 0.00
      Total Cost: 22.30
      Plan Rows: 1230
      Plan Width: 36
      Output:
      - "id"
      - "email"',
 '- Plan:
      Node Type: "Seq Scan"
      Relation Name: "profiles"
      Schema: "public"
      Alias: "profiles"
      Startup Cost: 0.00\
      Total Cost: 36.75
      Plan Rows: 11
      Plan Width: 8
      Output:
        - "id"
        - "user_id"
      Filter: "(profiles.user_id = 1)"'
]

formats: :text, :xml, :json, :yaml

explain_queries(options: [:analyze, :verbose, :costs, :buffers], format: :text)

User.all.preload(:profiles).eyeballs.explain_queries
["EXPLAIN (ANALYZE,VERBOSE,COSTS,BUFFERS,FORMAT TEXT) SELECT \"users\".* FROM \"users\"",
 "EXPLAIN (ANALYZE,VERBOSE,COSTS,BUFFERS,FORMAT TEXT) SELECT \"profiles\".* FROM \"profiles\" WHERE \"profiles\".\"user_id\" IN (1)"]

formats: :text, :xml, :json, :yaml

log_json(options: [:analyze, :verbose, :costs, :buffers])

Prints each JSON plan on a separate line. This is useful for command line processing with xargs and jq or gocmdpev

User.all.preload(:profiles).eyeballs.log_json

queries

User.all.preload(:profiles).eyeballs.queries
["SELECT \"users\".* FROM \"users\"",
 "SELECT \"profiles\".* FROM \"profiles\" WHERE \"profiles\".\"user_id\" IN (1)"]

to_hash_array(options: [:analyze, :verbose, :costs, :buffers])

User.all.eyeballs.to_hash_array
[[{"Plan"=>{
    "Node Type"=>"Seq Scan",
    "Relation Name"=>"users",
    "Schema"=>"public",
    "Alias"=>"users",
    "Startup Cost"=>0.0,
    "Total Cost"=>22.3,
    "Plan Rows"=>1230,
    "Plan Width"=>36,
    "Actual Startup Time"=>0.001,
    "Actual Total Time"=>0.001,
    "Actual Rows"=>1,
    "Actual Loops"=>1,
    "Output"=>["id", "email"],
    "Shared Hit Blocks"=>1,
    "Shared Read Blocks"=>0,
    "Shared Dirtied Blocks"=>0,
    "Shared Written Blocks"=>0,
    "Local Hit Blocks"=>0,
    "Local Read Blocks"=>0,
    "Local Dirtied Blocks"=>0,
    "Local Written Blocks"=>0,
    "Temp Read Blocks"=>0,
    "Temp Written Blocks"=>0,
    "I/O Read Time"=>0.0,
    "I/O Write Time"=>0.0},
    "Planning Time"=>0.014,
    "Triggers"=>[],
    "Execution Time"=>0.007}]
]

to_json(options: [:analyze, :verbose, :costs, :buffers])

alias for explain(format: :json)

to_s(options: [:analyze, :verbose, :costs, :buffers])

User.all.preload(:profiles).eyeballs.to_s
"Seq Scan on public.users  (cost=0.00..22.30 rows=1230 width=36) (actual time=0.001..0.002 rows=1 loops=1)
  Output: id, email
  Buffers: shared hit=1
Planning time: 0.010 ms
Execution time: 0.005 ms

Seq Scan on public.profiles  (cost=0.00..36.75 rows=11 width=8) (actual time=0.002..0.002 rows=1 loops=1)
  Output: id, user_id
  Filter: (profiles.user_id = 1)
  Buffers: shared hit=1
Planning time: 0.013 ms
Execution time: 0.006 ms"

Integration with gocmdpev

pg-eyeballs integrates with gocmdpev. If you have gocmdpev installed, you can use it in your Rails console:

User.all.preload(:profiles).eyeballs.gocmdpev

gocmdpev

You can also use pg-eyeballs and gocmdpev together from the command line. First, alias the included command. From inside the directory of you Rails project run

alias eyeballs=$(bundle show pg-eyeballs)/bin/gocmdpev

To use, also from inside your Rails project directory, run

eyeballs User.preload(:profiles)

Compatibility

pg-eyeballs has been tested with Rails versions 4 and 5. It may work on earlier versions, but I haven't tried it.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/bradurani/pg-eyeballs. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

Running the tests

First, bundle install. After this, if you are using the default database, first thing is to run createdb eyeballs_test. After this you can run the tests with rake.

License

The gem is available as open source under the terms of the MIT License.