yanxi 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/yanxi +10 -0
- data/lib/yanxi.rb +81 -0
- data/lib/yanxi/exporter.rb +38 -0
- data/lib/yanxi/request.rb +30 -0
- data/lib/yanxi/server.rb +81 -0
- data/lib/yanxi/version.rb +3 -0
- data/lib/yanxi/web/favicon.ico +0 -0
- data/lib/yanxi/web/index.html +66 -0
- data/lib/yanxi/web/static/css/app.85b83efe1d801160ed04da4ab170fb2a.css +2 -0
- data/lib/yanxi/web/static/css/app.85b83efe1d801160ed04da4ab170fb2a.css.map +1 -0
- data/lib/yanxi/web/static/data.json +1 -0
- data/lib/yanxi/web/static/js/app.b34914c4788e30883155.js +2 -0
- data/lib/yanxi/web/static/js/app.b34914c4788e30883155.js.map +1 -0
- data/lib/yanxi/web/static/js/manifest.e158313d260dfacd30ba.js +2 -0
- data/lib/yanxi/web/static/js/manifest.e158313d260dfacd30ba.js.map +1 -0
- data/lib/yanxi/web/static/js/vendor.323503aba588387c0c3d.js +14 -0
- data/lib/yanxi/web/static/js/vendor.323503aba588387c0c3d.js.map +1 -0
- data/screenshots/result1.png +0 -0
- data/yanxi.gemspec +35 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 093742b0259ddeda9212b539cdc36d01a0eadae2
|
4
|
+
data.tar.gz: 637c24cf92249e91348aa466dc3b8cbd7f10296a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4e42868614ca48bccc36fc78f5a6a3263c95df299a2ac06ef9e672babb58eb22729bc00abd10c31871a325a0445ee3318d119c854275bbe86815ca87dc63437a
|
7
|
+
data.tar.gz: a860cbac937326675f91efc78299010ebd4d5ea3027711943b7ee1a2df179ac0d8a976db274c9b740aad599697ebdb04ee9abcaa72dc723d8459fe5428f8630e
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
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 holin.he@gmail.com. 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/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Holin
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Yanxi
|
2
|
+
|
3
|
+
Parse Rails development.log file, get response time, database query and partial rendered time. Run an HTTP server to checkout and filter these infomation
|
4
|
+
|
5
|
+
![Screenshots 1](./screenshots/result1.png)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'yanxi'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install yanxi
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
```bash
|
26
|
+
bundle exec yanxi
|
27
|
+
```
|
28
|
+
|
29
|
+
## Development
|
30
|
+
|
31
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
32
|
+
|
33
|
+
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).
|
34
|
+
|
35
|
+
## Contributing
|
36
|
+
|
37
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/yanxi. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
38
|
+
|
39
|
+
|
40
|
+
## License
|
41
|
+
|
42
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
43
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "yanxi"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/bin/yanxi
ADDED
data/lib/yanxi.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require "yanxi/version"
|
2
|
+
require "yanxi/request"
|
3
|
+
require "yanxi/exporter"
|
4
|
+
|
5
|
+
module Yanxi
|
6
|
+
class Parser
|
7
|
+
def parse(log_file)
|
8
|
+
puts "parsing #{log_file}"
|
9
|
+
log_file = File.open(log_file, "r")
|
10
|
+
requests = []
|
11
|
+
current_request = Request.new
|
12
|
+
while line = log_file.gets
|
13
|
+
next unless select?(line)
|
14
|
+
line = clean(line)
|
15
|
+
if new_request?(line)
|
16
|
+
current_request = Request.new
|
17
|
+
requests << current_request
|
18
|
+
end
|
19
|
+
|
20
|
+
if current_request != nil
|
21
|
+
# fill request
|
22
|
+
if line =~ /Started (:?GET|POST) "(.+?)" for/
|
23
|
+
current_request.path = $2
|
24
|
+
elsif line =~ /Processing by (.+?) as/
|
25
|
+
current_request.action = $1
|
26
|
+
elsif line =~ /Parameters: (\{.+\})/
|
27
|
+
current_request.params = $1
|
28
|
+
elsif line =~ /\(([\d|\.]+)ms\).+((:?SELECT|UPDATE|DELETE|INSERT).+)\n/
|
29
|
+
arr = [$1, $2]
|
30
|
+
current_request.sqls << arr
|
31
|
+
elsif line =~ /Rendered (.+) \(([\d|\.]+)ms\)\n/
|
32
|
+
arr = [$2, $1]
|
33
|
+
current_request.partials << arr if available_partial(arr.last)
|
34
|
+
elsif line =~ /(Completed 200 OK in ([\d|\.]+)ms \(Views: ([\d|\.]+)ms \| ActiveRecord: ([\d|\.]+)ms\))\n/
|
35
|
+
arr = [$1, $2, $3, $4]
|
36
|
+
current_request.complete = arr
|
37
|
+
current_request = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
log_file.close
|
43
|
+
|
44
|
+
json_requests = {}
|
45
|
+
|
46
|
+
requests.each do |r|
|
47
|
+
request = json_requests[r.uniq_key] || r
|
48
|
+
next if r.complete.nil?
|
49
|
+
if r.complete[1] > request.complete[1]
|
50
|
+
request = r
|
51
|
+
end
|
52
|
+
|
53
|
+
json_requests[request.uniq_key] = request
|
54
|
+
end
|
55
|
+
|
56
|
+
json_requests.keys.each do |key|
|
57
|
+
json_requests[key] = json_requests[key].to_json
|
58
|
+
end
|
59
|
+
|
60
|
+
Exporter.new.export(json_requests)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def select?(line)
|
65
|
+
return false if line =~ /CACHE/
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def available_partial(partial_path)
|
70
|
+
!partial_path.start_with?("/") and !partial_path.index("/_").nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def new_request?(line)
|
74
|
+
line =~ /Started (GET|POST) /
|
75
|
+
end
|
76
|
+
|
77
|
+
def clean(line)
|
78
|
+
line.gsub(/\e\[(?:[0-9];?)+m/, '')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "yanxi"
|
2
|
+
require "json"
|
3
|
+
require "yanxi/server"
|
4
|
+
|
5
|
+
module Yanxi
|
6
|
+
class Exporter
|
7
|
+
attr_accessor :requests
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
end
|
11
|
+
|
12
|
+
def export(json_requests)
|
13
|
+
src_dir = File.join(File.dirname(__FILE__), "web")
|
14
|
+
dest_dir = File.join(Dir.pwd, "tmp", "yanxi_export")
|
15
|
+
data_file = File.join(dest_dir, "static", "data.json")
|
16
|
+
FileUtils.cp_r(src_dir, dest_dir)
|
17
|
+
json = {
|
18
|
+
total: json_requests.size,
|
19
|
+
per_page: json_requests.size,
|
20
|
+
current_page: 1,
|
21
|
+
last_page: 1,
|
22
|
+
next_page_url: nil,
|
23
|
+
prev_page_url: nil,
|
24
|
+
from: 1,
|
25
|
+
to: 1,
|
26
|
+
data: json_requests.values
|
27
|
+
}
|
28
|
+
File.open(data_file, "w"){ |f| f.write(JSON.dump(json)) }
|
29
|
+
puts "Please checkout export at #{dest_dir}"
|
30
|
+
port = 3000 + (rand*10000).to_i
|
31
|
+
url = "http://0.0.0.0:#{port}"
|
32
|
+
puts "visit: #{url}"
|
33
|
+
# run http server
|
34
|
+
EmbedServer.new(dest_dir, port: port).run
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "yanxi"
|
2
|
+
|
3
|
+
module Yanxi
|
4
|
+
class Request
|
5
|
+
attr_accessor :path, :action, :sqls, :partials, :params, :complete
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@sqls = []
|
9
|
+
@partials = []
|
10
|
+
@params = ""
|
11
|
+
end
|
12
|
+
|
13
|
+
def uniq_key
|
14
|
+
"#{action}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_json
|
18
|
+
{
|
19
|
+
id: "#{(rand*100000).to_i}",
|
20
|
+
path: path,
|
21
|
+
action: action,
|
22
|
+
sqls: sqls,
|
23
|
+
partials: partials,
|
24
|
+
params: params,
|
25
|
+
complete: complete,
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/yanxi/server.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Yanxi
|
5
|
+
|
6
|
+
# Usage:
|
7
|
+
## => EmbedServer.new(web_root).run
|
8
|
+
class EmbedServer
|
9
|
+
|
10
|
+
attr_accessor :web_root, :port
|
11
|
+
|
12
|
+
def initialize(web_root, options = {})
|
13
|
+
@web_root = web_root
|
14
|
+
@port = options[:port] || 7000
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
server = TCPServer.new('0.0.0.0', port)
|
19
|
+
loop do
|
20
|
+
socket = server.accept
|
21
|
+
request_line = socket.gets
|
22
|
+
STDERR.puts request_line
|
23
|
+
path = requested_file(request_line)
|
24
|
+
path = File.join(path, 'index.html') if File.directory?(path)
|
25
|
+
if File.exist?(path) && !File.directory?(path)
|
26
|
+
File.open(path, 'rb') do |file|
|
27
|
+
socket.print "HTTP/1.1 200 OK\r\n" \
|
28
|
+
"Content-Type: #{content_type(file)}\r\n" \
|
29
|
+
"Content-Length: #{file.size}\r\n" \
|
30
|
+
"Connection: close\r\n"
|
31
|
+
socket.print "\r\n"
|
32
|
+
IO.copy_stream(file, socket)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
message = "File not found\n"
|
36
|
+
socket.print "HTTP/1.1 404 Not Found\r\n" \
|
37
|
+
"Content-Type: text/plain\r\n" \
|
38
|
+
"Content-Length: #{message.size}\r\n" \
|
39
|
+
"Connection: close\r\n"
|
40
|
+
socket.print "\r\n"
|
41
|
+
socket.print message
|
42
|
+
end
|
43
|
+
socket.close
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def content_type(path)
|
49
|
+
content_type_mapping = {
|
50
|
+
'html' => 'text/html',
|
51
|
+
'txt' => 'text/plain',
|
52
|
+
'json' => 'application/json', # add json support
|
53
|
+
'png' => 'image/png',
|
54
|
+
'jpg' => 'image/jpeg',
|
55
|
+
'gif' => 'image/gif'
|
56
|
+
}
|
57
|
+
|
58
|
+
default_content_type = 'application/octet-stream'
|
59
|
+
ext = File.extname(path).split('.').last
|
60
|
+
content_type_mapping.fetch(ext, default_content_type)
|
61
|
+
end
|
62
|
+
|
63
|
+
def requested_file(request_line)
|
64
|
+
request_uri = request_line.split(' ')[1]
|
65
|
+
path = URI.unescape(URI(request_uri).path)
|
66
|
+
cleanurl = []
|
67
|
+
parts = path.split('/')
|
68
|
+
|
69
|
+
parts.each do |part|
|
70
|
+
next if part.empty? || part == '.'
|
71
|
+
part == '..' ? cleanurl.pop : cleanurl << part
|
72
|
+
end
|
73
|
+
File.join(web_root, *cleanurl)
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
|