https://github.com/neo4jrb/neolytics
A gem to record data about Ruby code execution in Neo4j for analysis
https://github.com/neo4jrb/neolytics
Last synced: about 2 months ago
JSON representation
A gem to record data about Ruby code execution in Neo4j for analysis
- Host: GitHub
- URL: https://github.com/neo4jrb/neolytics
- Owner: neo4jrb
- License: mit
- Created: 2015-12-28T08:18:32.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2016-01-12T07:52:47.000Z (over 9 years ago)
- Last Synced: 2025-04-16T21:26:25.106Z (2 months ago)
- Language: Ruby
- Size: 254 KB
- Stars: 18
- Watchers: 7
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# Neolytics
Neolytics is a gem which allows you to analyze a section of your code using Neo4j. The data model used is described below and a number of example queries are given (you can [suggest an idea](https://github.com/neo4j-examples/ruby_code_analytics/labels/idea%20for%20query) for a new query).
There is also a [ruby_code_analytics](https://github.com/neo4j-examples/ruby_code_analytics) Rails application which can take data generated by neolytics and allow you to browse it with a nice UI.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'neolytics'
```And then execute:
$ bundle
Or install it yourself as:
$ gem install neolytics
## Usage
All you need to do to use Neolytics is
neo4j_session = Neo4j::Session.open(:server_db, 'http://neo4j:pass@localhost:7474')
# or
neo4j_session = Neo4j::Session.currentNeolytics.record_execution(neo4j_session) do
# Code that you want to analyze here
endDuring the code execution
## Model:
Neolytics generates the following structures:
### Abstract Syntax Trees

### TracePoint execution tracking

### Objects (including recursively browsed class and modules)

### Links between these domains
TracePoints are linked to AST nodes:

TracePoints are linked to objects (arguments, return values, variable values, context objects):

## Example queries:
### Queries using the abstract syntax trees:
#### Get all assignments
```cypher
MATCH (a:ASTNode)
WHERE a.type IN ['lvasgn', 'ivasgn', 'casgn', 'op_asgn']
RETURN a.type, a.name, a.operator, a.expression
ORDER BY a.type, a.name
```[Example Output](examples/output/assignments.csv)
#### All defined methods:
```cypher
MATCH (def:ASTNode {type: 'def'})
WITH def
ORDER BY def.file_path, def.first_line
LIMIT 50
MATCH shortestPath((def)-[:HAS_PARENT*]->(class:ASTNode {type: 'class'}))
RETURN def.file_path +':'+ def.first_line AS line, class.name AS class, def.name AS method
```[Example Output](examples/output/ast.csv)
#### Assignment-Branch-Condition (ABC) Metric
```cypher
MATCH (def:ASTNode {type: 'def'})OPTIONAL MATCH (def)<-[:HAS_PARENT*]-(assignment:ASTNode)
WHERE assignment.type IN ['lvasgn', 'ivasgn', 'casgn', 'op_asgn']
WITH def, count(assignment) AS aOPTIONAL MATCH (def)<-[:HAS_PARENT*]-(branch:ASTNode)
WHERE branch.type = 'send'
WITH def, a, count(branch) AS bOPTIONAL MATCH (def)<-[:HAS_PARENT*]-(condition:ASTNode)
WHERE condition.type IN ['if', 'while', 'until', 'for', 'rescue', 'when', 'and', 'or']
WITH def, a, b, count(condition) AS cRETURN def.name, def.file_path, def.first_line, a, b, c, sqrt((a*a) + (b*b) + (c*c)) AS abc
ORDER BY abc DESC
```[Example Output](examples/output/abc.csv)
#### Cyclomatic Complexity
```cypher
MATCH (def:ASTNode {type: 'def'})
OPTIONAL MATCH (def)<-[:HAS_PARENT*]-(condition:ASTNode)
WHERE condition.type IN ['begin', 'if', 'while', 'until', 'for', 'rescue', 'when', 'and', 'or']
RETURN def.name, def.file_path, def.first_line, count(condition)
ORDER BY count(condition) DESC
LIMIT 50
```[Example Output](examples/output/cyclomatic_complexity.csv)
#### All args / vars / statics / etc... in a class
```cypher
MATCH (:ASTNode {name: 'Numeric', type: 'class'})<-[:HAS_PARENT*]-(n:ASTNode)
WHERE n.type IN ['def', 'arg', 'optarg', 'restarg', 'lvar', 'const', 'sym']
RETURN n.type, n.expression, n.name
ORDER BY n.type, n.expression
```[Example Output](examples/output/all_args_vars_statics.csv)
### TracePoint queries
#### Time spent by method
```cypher
MATCH (tp:TracePoint)
WITH tp.path AS path, tp.lineno AS line, tp.defined_class AS class, tp.method_id AS method_id, sum(tp.execution_time) AS sum, count(tp) AS count
ORDER BY sum(tp.execution_time) DESC
RETURN path +':'+ line AS line, class +'#'+ method_id AS method, sum, count, sum / count AS average
LIMIT 50
```[Example Output](examples/output/time_spent_by_method.csv)
#### Common ancestor
```cypher
MATCH
(tp1:TracePoint {defined_class: 'ActiveSupport::Autoload', method_id: 'autoload_at'}),
(tp2:TracePoint {defined_class: 'ActiveSupport::Inflector', method_id: 'underscore'})
WITH tp1, tp2 LIMIT 1
MATCH
path1=(tp1)-[:HAS_PARENT*0..]->(common_ancestor),
path2=(common_ancestor)<-[:HAS_PARENT*0..]-(tp2:TracePoint)
RETURN tp1, tp2, common_ancestor, length(path1), length(path2)
```[Example Output](examples/output/common_ancestor.csv)
#### See all arguments and return values for methods
```cypher
MATCH (start_tp:TracePoint)
WHERE start_tp.event = 'call'
OPTIONAL MATCH (start_tp)-[argument_rel:RECEIVED_ARGUMENT]->(arg:Object)-[:IS_A]->(class:Object)
WITH
start_tp,
collect(argument_rel.argument_name +': '+ arg.ruby_inspect +' ('+ class.ruby_inspect +')') AS argumentsOPTIONAL MATCH (start_tp)<-[:STARTED_AT]-(return_tp:TracePoint)-[:RETURNED]->(o:Object)-[:IS_A]-(class:Object)
RETURN
start_tp.path +':'+ start_tp.lineno AS line,
start_tp.defined_class +'#'+ start_tp.method_id AS method,
arguments,
o.ruby_inspect +' ('+ class.ruby_inspect +')' AS return_object
ORDER BY start_tp.execution_index
LIMIT 30
```[Example Output](examples/output/arguments_and_return_values.csv)
#### Show all returns values from a particular superclass (i.e. Asking for `Numeric` gives `Fixnum`, `Rational`, `BigDecimal`, etc... types)
```cypher
MATCH (tp:TracePoint)-[:RETURNED]->(o:Object)-[:IS_A]-(class:Object)-[:HAS_SUPERCLASS*]->(superclass:Object {ruby_inspect: 'Numeric'})
WHERE tp.event IN ['return', 'c_return']
RETURN tp.path +':'+ tp.lineno AS line, tp.defined_class +'#'+ tp.method_id AS method, o.ruby_inspect +' ('+ class.ruby_inspect +')' AS return_value
ORDER BY tp.execution_index
```[Example Output](examples/output/numeric_return_values.csv)
### Object space queries:
#### Show class hierarchy for `Numeric` class:
```cypher
MATCH (class:Object)-[:HAS_SUPERCLASS*]->(superclass:Object {ruby_inspect: 'Numeric'})
RETURN *
```**Example Output:**

### Combination queries
#### Combining cyclomatic complexity and run-time metrics
```cypher
MATCH (tp:TracePoint)
WITH sum(tp.execution_time) AS total_execution_timeMATCH (node:ASTNode {type: 'def'})
OPTIONAL MATCH (node)<-[:HAS_PARENT*]-(condition:ASTNode)
WHERE condition.type IN ['begin', 'if', 'while', 'until', 'for', 'rescue', 'when', 'and', 'or']
WITH node, count(condition) AS cyclomatic_complexity, total_execution_timeMATCH (node)<-[:HAS_AST_NODE]-(tp:TracePoint)<-[:STARTED_AT]-(return_tp:TracePoint)
WITH
cyclomatic_complexity,
total_execution_time,
tp.path + ':' + tp.lineno + ' (' + return_tp.defined_class + '#' + return_tp.method_id + ')' AS method,
count(tp) AS executions,
sum(return_tp.execution_time) AS total_method_execution_timeRETURN
method,
cyclomatic_complexity,
executions,
total_method_execution_time,
100.0 * (total_method_execution_time / total_execution_time) AS percentage_of_total_time,
total_method_execution_time / executions AS average_execution_timeORDER BY total_method_execution_time DESC
```[Example Output](examples/output/cyclomatic_complexity_and_runtime.csv)
## 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](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/neolytics. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).