plog 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ kazu.test
7
+ objects
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Kazuyoshi Tlacaelel
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,148 @@
1
+ = Plog
2
+
3
+ Ruby on Rails production log statistics generator.
4
+
5
+ by Kazuyoshi Tlacaelel.
6
+
7
+ $ plog my_directory_with_production_logs/
8
+ $ ls plog/
9
+ objects statistics.txt
10
+
11
+
12
+ == Copyright
13
+
14
+ Copyright (c) 2009 Kazuyoshi Tlacaelel. See LICENSE for details.
15
+
16
+ == Comming soon!
17
+
18
+ comming soon to a terminal near you!
19
+
20
+ * sorting:
21
+ * more statistics
22
+ (hits, number of hits per response type like 200, 302 etc)
23
+
24
+ May be... but don't expect any of these to become a reality
25
+ I love programming and tons of ideas flow in my head with posibilities
26
+ but I do not have the time! :(
27
+
28
+ * Different export drivers?
29
+ (should be another plugin)
30
+
31
+ * cli options abstracted into classes so they can be aggregated with ease
32
+ (this might be cool)
33
+
34
+ * google charts integration
35
+ (is too much for this api)
36
+ (may be as a different plugin)
37
+
38
+ == Dependancies:
39
+
40
+ * FileUtils
41
+ * MD5
42
+ * File
43
+ * Dir
44
+ * URI
45
+ * rubygems 1.3.3
46
+
47
+ == Features:
48
+
49
+ Compiles statistics in a easy to read manner
50
+
51
+
52
+ Hits Time Avg-Time DbTime Avg-DB View Avg-View Url
53
+ 93 18057 0.194 5775 0.062 10437 0.112 /users/[0-9]
54
+ 12 277 0.023 193 0.016 0 0 /user_themes/update
55
+ 1 290 0.29 107 0.107 175 0.175 /posts
56
+ 3 99 0.033 57 0.019 0 0 /comments
57
+ 82 11922 0.145 5969 0.072 4979 0.06 /user/[0-9]/comments
58
+ 1868 1301006 0.696 623246 0.333 491474 0.263 /user/[0-9]/posts?
59
+ 1 72 0.072 39 0.039 0 0 /confirm_destroy_account/[0-9]?
60
+ 797 971882 1.219 414829 0.52 423130 0.53 /user/[0-9]/articles/friends?
61
+ 122 20830 0.17 7081 0.058 10291 0.084 /user/[0-9]/topics?
62
+
63
+ == Improvized twicks:
64
+
65
+ You can improvize some quick sorting using the command line like:
66
+
67
+ cat plog/statistics.txt | head -n1 > some_file.txt
68
+ cat plog/statistics.txt | sort -nr >> some_file.txt
69
+
70
+ This will create a new file with sorted results by hits for you!
71
+
72
+ Hits Time Avg-Time DbTime Avg-DB View Avg-View Url
73
+ 1868 1301006 0.696 623246 0.333 491474 0.263 /user/[0-9]/posts?
74
+ 797 971882 1.219 414829 0.52 423130 0.53 /user/[0-9]/articles/friends?
75
+ 122 20830 0.17 7081 0.058 10291 0.084 /user/[0-9]/topics?
76
+ 93 18057 0.194 5775 0.062 10437 0.112 /users/[0-9]
77
+ 82 11922 0.145 5969 0.072 4979 0.06 /user/[0-9]/comments
78
+ 12 277 0.023 193 0.016 0 0 /user_themes/update
79
+ 3 99 0.033 57 0.019 0 0 /comments
80
+ 1 72 0.072 39 0.039 0 0 /confirm_destroy_account/[0-9]?
81
+ 1 290 0.29 107 0.107 175 0.175 /posts
82
+
83
+ == Installation
84
+
85
+ $ gem sources -a http://gems.github.com
86
+ $ gem install ktlacaelel-plog
87
+
88
+ == Known bugs:
89
+ none, so far..
90
+
91
+ == License:
92
+ MIT. See the "LICENSE" file for details.
93
+
94
+ == Participate
95
+
96
+ * Send me a brief message
97
+ * Fork the project.
98
+ * Add tests for it. This is important so I don't break it in a
99
+ future version unintentionally.
100
+ * Make your feature addition or bug fix.
101
+ * Commit, do not mess with rakefile, version, or history.
102
+ (if you want to have your own version, that is fine but
103
+ bump version in a commit by itself I can ignore when I pull)
104
+ * Send me a pull request. Bonus points for topic branches.
105
+
106
+ == Output:
107
+
108
+ All output is packed in a plog directory, from wherever you run
109
+ the "plog" executable.
110
+
111
+ Inside this directory you'll find a directory called objects.
112
+ wich contains many small files called "object-files"
113
+ this files contain all the parsed statistics of you log files.
114
+
115
+ The information is scattered and can be reused for future compilations
116
+ meaning that new compilations data will merge with the existing one.
117
+
118
+ all data in these "object-files" is read and summarized into a
119
+ statistics.txt file found inside of the plog directory.
120
+
121
+ == Tests:
122
+
123
+ * 70% tested, with shoulda
124
+ * all core areas of the api are tested, and tests pass!
125
+ * only the cli part of the api has no tests, but will be implemented soon
126
+
127
+ == Usage:
128
+
129
+ The "plog" ( production log ) executable receives one option.
130
+ This must be a directory containing one or more production logs.
131
+ Only logs in the first level will be parsed.
132
+ Recursive reading is not allowed
133
+
134
+ $ plog directory_with_logs/
135
+
136
+ == Works on:
137
+
138
+ * OSX Darwin utopia.local 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:55:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_I386 i386
139
+ * UBUNTU Linux utopia.local 2.6.28-11-server #42-Ubuntu SMP Fri Apr 17 02:45:36 UTC 2009 x86_64 GNU/Linux
140
+
141
+ * OSX ruby 1.8.6 (2008-08-11 patchlevel 287) [universal-darwin9.0]
142
+ * UBUNTU ruby 1.8.7 (2009-06-12 patchlevel 174) [x86_64-linux]
143
+
144
+ * rubygems 1.3.4
145
+ * rubygems 1.3.3
146
+
147
+ == The end.
148
+
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "plog"
8
+ gem.summary = %Q{Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel}
9
+ gem.description = %Q{Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel}
10
+ gem.email = "kazu.dev@gmail.com"
11
+ gem.homepage = "http://github.com/ktlacaelel/plog"
12
+ gem.authors = ["Kazuyoshi Tlacaelel"]
13
+ gem.add_development_dependency "thoughtbot-shoulda"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/*_test.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ if File.exist?('VERSION')
47
+ version = File.read('VERSION')
48
+ else
49
+ version = ""
50
+ end
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "plog #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.0
@@ -0,0 +1,22 @@
1
+ #!/home/jognote/ruby/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'plog'
5
+
6
+ banner = '
7
+ ERROR!
8
+ No logs given, I have nothing to do!
9
+
10
+ HELP?
11
+ The "plog" ( production log ) executable receives one option.
12
+ This must be a directory containing one or more production logs.
13
+ Only logs in the first level will be parsed.
14
+ Recursive reading is not allowed
15
+
16
+ USAGE:
17
+ $ plog directory_with_logs/
18
+
19
+ '
20
+
21
+ abort banner if ARGV.size != 1
22
+ Plog::Cli.new(ARGV[0], 'plog').run!
@@ -0,0 +1,119 @@
1
+ module Plog
2
+
3
+ class Cli
4
+
5
+ attr_reader :source_directory, :log_files
6
+
7
+ STATS_FILE = 'statistics.txt'
8
+
9
+ # ==========================================================================
10
+ # CLIENT INTERFACE
11
+ # ==========================================================================
12
+
13
+ def initialize(source_directory, target_directory)
14
+ @log_files = []
15
+ @source_directory = source_directory
16
+ @target_directory = target_directory
17
+ check_directory_consistency!
18
+ preload_log_files!
19
+ end
20
+
21
+ def run!
22
+ stdout count_of_logs_banner(@log_files.size)
23
+ stdout notice_banner
24
+ create_object_files
25
+ destroy_old_log_file
26
+ append_headers_to_log_file
27
+ parse_object_files
28
+ end
29
+
30
+ # ==========================================================================
31
+ # INTERNAL INTERFACE
32
+ # ==========================================================================
33
+
34
+ protected
35
+
36
+ def trim(string)
37
+ return '' unless string.is_a? String
38
+ string.gsub(/^\s+/, '').gsub(/\s+$/, '')
39
+ end
40
+
41
+ def create_object_files
42
+ @log_files.each do |log_file|
43
+ stdout parsing_log_file_banner(log_file.name)
44
+ log_file.parse_completed_lines!
45
+ end
46
+ end
47
+
48
+ def parse_object_files
49
+ Dir.glob(object_file_pattern).each do |object_file|
50
+ file = File.new(statistic_file_path, 'a+')
51
+ file.puts trim(ObjectFile.new(object_file).export)
52
+ file.close
53
+ end
54
+ end
55
+
56
+ def object_file_pattern
57
+ File.join(@target_directory, 'objects') + '/*'
58
+ end
59
+
60
+ def statistic_file_path
61
+ File.join(@target_directory, STATS_FILE)
62
+ end
63
+
64
+ def destroy_old_log_file
65
+ FileUtils.touch statistic_file_path
66
+ FileUtils.rm statistic_file_path
67
+ end
68
+
69
+ def append_headers_to_log_file
70
+ file = File.new(statistic_file_path, 'a+')
71
+ file.puts trim(ObjectFile.formated_headers)
72
+ file.close
73
+ end
74
+
75
+ def check_directory_consistency!
76
+ abort directory_not_found_banner unless File.exist? @source_directory
77
+ end
78
+
79
+ def preload_log_files!
80
+ Dir.glob(File.join(@source_directory, '*.log')).each do |log_file|
81
+ @log_files << LogFile.new(log_file, @target_directory)
82
+ stdout loading_log_file_banner(log_file)
83
+ end
84
+ end
85
+
86
+ def stdout(string)
87
+ puts ' ---> ' + string
88
+ end
89
+
90
+ # ==========================================================================
91
+ # BANNERS
92
+ # ==========================================================================
93
+
94
+ def notice_banner
95
+ 'Parsing logs...
96
+ This may take a long-while, go get yourself a coffee!
97
+ While I hanlde this stuff for you.
98
+ '
99
+ end
100
+
101
+ def count_of_logs_banner(size)
102
+ 'Count of loaded logs : %s ' % size
103
+ end
104
+
105
+ def parsing_log_file_banner(file)
106
+ 'Parsing log located at : %s ' % file
107
+ end
108
+
109
+ def loading_log_file_banner(file)
110
+ 'Initializing log file: %s' % file
111
+ end
112
+
113
+ def directory_not_found_banner
114
+ 'No such dir: %s' % @source_directory
115
+ end
116
+
117
+ end
118
+
119
+ end
@@ -0,0 +1,109 @@
1
+ module Plog
2
+
3
+ class CompletedLine
4
+
5
+ TOTAL_TIME_REGEX = /^(Completed in )(\d+)ms/ # \2
6
+ VIEW_TIME_REGEX = /([^\(]+)([^V]+)(View: )(\d+)(.*)/ # \4
7
+ DB_TIME_REGEX = /([^\(]+)([^D]+)(DB: )(\d+)(.*)/ # \4
8
+ URL_REGEX = /([^\[]+)(\[)([^\]]+)(.*)/
9
+ STATUS_REGEX = /(\s)(\d+)(\s)(\w+)(\s)(\[)(.*)/ # \2, \4
10
+ COMPLETED_TIME_REGEX = /^Completed/
11
+
12
+ def self.url
13
+ URL.new(@url)
14
+ end
15
+
16
+ def self.valid?
17
+ completed_line?
18
+ end
19
+
20
+ def self.completed_line?
21
+ return false unless @line.is_a? String
22
+ return false unless COMPLETED_TIME_REGEX =~ @line
23
+ return false unless DB_TIME_REGEX =~ @line
24
+ return false if @line.size < 10
25
+ true
26
+ end
27
+
28
+ def self.read!(string_line)
29
+ @line = string_line
30
+ validate
31
+ end
32
+
33
+ def self.db_time
34
+ @db_time
35
+ end
36
+
37
+ def self.view_time
38
+ @view_time
39
+ end
40
+
41
+ def self.total_time
42
+ @total_time
43
+ end
44
+
45
+ def self.status_string
46
+ @status_string
47
+ end
48
+
49
+ def self.status_number
50
+ @status_number
51
+ end
52
+
53
+ def self.to_csv
54
+ [total_time, view_time, db_time, url.simplify].join(',')
55
+ end
56
+
57
+ def self.merge(view, db)
58
+ @view_time += view.to_i
59
+ @db_time += db.to_i
60
+ @total_time = (@view_time + @db_time)
61
+ end
62
+
63
+ protected
64
+
65
+ def self.validate
66
+ return unless completed_line?
67
+ fragmentize!
68
+ extract_time!
69
+ extract_url!
70
+ end
71
+
72
+ def self.fragmentize!
73
+ @first_fragment, @second_fragment = @line.split('|')
74
+ end
75
+
76
+ def self.extract_time!
77
+ @total_time = extract_total_time
78
+ @view_time = extract_view_time
79
+ @db_time = extract_db_time
80
+ @status_number, @status_string = extract_status_number_and_string
81
+ nil
82
+ end
83
+
84
+ def self.extract_total_time
85
+ return 0 unless TOTAL_TIME_REGEX =~ @first_fragment
86
+ @first_fragment.gsub(TOTAL_TIME_REGEX, '\2').to_i
87
+ end
88
+
89
+ def self.extract_db_time
90
+ return 0 unless DB_TIME_REGEX =~ @first_fragment
91
+ @first_fragment.gsub(DB_TIME_REGEX, '\4').to_i
92
+ end
93
+
94
+ def self.extract_view_time
95
+ return 0 unless VIEW_TIME_REGEX =~ @first_fragment
96
+ @first_fragment.gsub(VIEW_TIME_REGEX, '\4').to_i
97
+ end
98
+
99
+ def self.extract_status_number_and_string
100
+ @second_fragment.gsub(STATUS_REGEX, '\2,\4').split(',')
101
+ end
102
+
103
+ def self.extract_url!
104
+ @url = @second_fragment.gsub(URL_REGEX, '\3')
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,52 @@
1
+ module Plog
2
+
3
+ class LogFile < File
4
+
5
+ attr_accessor :name
6
+
7
+ def initialize(file, dump_dir)
8
+ @dump_dir = dump_dir
9
+ check_dump_dir_consistency!
10
+ super(file, 'r')
11
+ @name = file
12
+ end
13
+
14
+ def check_dump_dir_consistency!
15
+ unless File.exist? objects_recipient_path
16
+ FileUtils.mkdir_p objects_recipient_path
17
+ end
18
+ end
19
+
20
+ def validate(file)
21
+ abort 'File not found: ' + file.inspect unless File.exist? file
22
+ end
23
+
24
+ def parse_completed_lines!
25
+ each_line do |line|
26
+ CompletedLine.read! line
27
+ parse_completed_line if CompletedLine.valid?
28
+ end
29
+ end
30
+
31
+ def parse_completed_line
32
+ of = ObjectFile.new(object_path, 'w+')
33
+ of.simplified_url = CompletedLine.url.simplify
34
+ of.append_total_time CompletedLine.total_time
35
+ of.append_db_time CompletedLine.db_time
36
+ of.append_view_time CompletedLine.view_time
37
+ of.append_hits 1
38
+ of.save_changes!
39
+ of.close
40
+ end
41
+
42
+ def object_path
43
+ File.join(objects_recipient_path, CompletedLine.url.hashify)
44
+ end
45
+
46
+ def objects_recipient_path
47
+ @objects_recipient_path ||= File.join(@dump_dir, 'objects')
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,165 @@
1
+ module Plog
2
+
3
+ class ObjectFile < File
4
+
5
+ DEFAULT_VALUES = {
6
+ :@total_hits => 0,
7
+ :@total_time => 0,
8
+ :@view_time => 0,
9
+ :@db_time => 0,
10
+ :@simplified_url => 'default'
11
+ }
12
+
13
+ KEY_ORDER = [
14
+ :@total_hits,
15
+ :@total_time,
16
+ :@view_time,
17
+ :@db_time,
18
+ :@simplified_url
19
+ ]
20
+
21
+ attr_accessor :simplified_url
22
+ attr_reader :view_time, :total_time, :db_time, :total_hits
23
+
24
+ def initialize(*args)
25
+ raw_data(args.first)
26
+ super(*args)
27
+ setup!
28
+ end
29
+
30
+ def setup!
31
+ unpack_default_values if ObjectFile.zero? path
32
+ load_changes!
33
+ end
34
+
35
+ def unpack_default_values
36
+ DEFAULT_VALUES.each do |k, v|
37
+ instance_variable_set k, v
38
+ end
39
+ end
40
+
41
+ def append_view_time(value)
42
+ @view_time += value.to_i
43
+ end
44
+
45
+ def append_db_time(value)
46
+ @db_time += value.to_i
47
+ end
48
+
49
+ def append_total_time(value)
50
+ @total_time += value
51
+ end
52
+
53
+ def append_hits(value)
54
+ @total_hits += value
55
+ end
56
+
57
+ def save_changes!
58
+ puts serialize_changes
59
+ end
60
+
61
+ def serialize_changes
62
+ KEY_ORDER.collect { |key| instance_variable_get key }.join(',')
63
+ end
64
+
65
+ def load_changes!
66
+ return if @raw_data == ''
67
+ @total_hits, @total_time, @view_time, @db_time, @simplified_url = @raw_data.split(',')
68
+ intify!
69
+ end
70
+
71
+ def intify!
72
+ @total_hits = @total_hits.to_i
73
+ @total_time = @total_time.to_i
74
+ @db_time = @db_time.to_i
75
+ @view_time = @view_time.to_i
76
+ end
77
+
78
+ def raw_data(some_string)
79
+ if File.exist? some_string
80
+ @raw_data = File.new(some_string).read
81
+ else
82
+ @raw_data = ''
83
+ end
84
+ end
85
+
86
+ def export_settings
87
+ [
88
+ [@total_hits, [:left, 10]],
89
+ [total_time_in_secs, [:left, 10]],
90
+ [avg_total_time, [:left, 10]],
91
+ [db_time_in_secs, [:left, 10]],
92
+ [avg_db_time, [:left, 10]],
93
+ [view_time_in_secs, [:left, 10]],
94
+ [avg_view_time, [:left, 10]],
95
+ [simplified_url, [:left, 40]],
96
+ ]
97
+ end
98
+
99
+ def self.export_header_settings
100
+ [
101
+ ['Hits', [:left, 10]],
102
+ ['Time', [:left, 10]],
103
+ ['Avg-Time', [:left, 10]],
104
+ ['DbTime', [:left, 10]],
105
+ ['Avg-DB', [:left, 10]],
106
+ ['View', [:left, 10]],
107
+ ['Avg-View', [:left, 10]],
108
+ ['Url', [:left, 40]],
109
+ ]
110
+ end
111
+
112
+ def self.formated_headers
113
+ export_header_settings.collect do |value, setting|
114
+ if setting.first == :left
115
+ value.to_s.ljust(setting[1])
116
+ else
117
+ value.to_s.rjust(setting[1])
118
+ end
119
+ end.join('') + "\n"
120
+ end
121
+
122
+ def export_headers
123
+ export_header_settings.collect { |header| header.ljust(20) }.join('')
124
+ end
125
+
126
+ def export
127
+ export_settings.collect do |value, setting|
128
+ if setting.first == :left
129
+ value.to_s.ljust(setting[1])
130
+ else
131
+ value.to_s.rjust(setting[1])
132
+ end
133
+ end.join('')
134
+ end
135
+
136
+ def avg_total_time
137
+ return 0 if @total_time == 0
138
+ (@total_time / @total_hits / 1000.0)
139
+ end
140
+
141
+ def avg_view_time
142
+ return 0 if @view_time == 0
143
+ (@view_time / @total_hits / 1000.0)
144
+ end
145
+
146
+ def avg_db_time
147
+ return 0 if @db_time == 0
148
+ (@db_time / @total_hits / 1000.0)
149
+ end
150
+
151
+ def total_time_in_secs
152
+ @total_time / 1000.0
153
+ end
154
+
155
+ def db_time_in_secs
156
+ @db_time / 1000.0
157
+ end
158
+
159
+ def view_time_in_secs
160
+ @view_time / 1000.0
161
+ end
162
+
163
+ end
164
+
165
+ end
@@ -0,0 +1,13 @@
1
+
2
+ # required external libraries
3
+ require 'fileutils'
4
+ require 'md5'
5
+ require 'uri'
6
+
7
+ # required internal libraries
8
+ require 'cli'
9
+ require 'completed_line'
10
+ require 'log_file'
11
+ require 'object_file'
12
+ require 'url'
13
+
@@ -0,0 +1,36 @@
1
+ module Plog
2
+
3
+ class URL
4
+
5
+ def initialize(url)
6
+ @url = url
7
+ @obj = URI.parse URI.escape(url)
8
+ end
9
+
10
+ def to_s
11
+ @url
12
+ end
13
+
14
+ def simplify
15
+ simplify_url = @url.dup
16
+ simplifiers.each { |k, v| simplify_url.gsub!(k, v) if k =~ simplify_url }
17
+ simplify_url
18
+ end
19
+
20
+ def hashify
21
+ ::MD5.hexdigest simplify
22
+ end
23
+
24
+ protected
25
+
26
+ def simplifiers
27
+ {
28
+ /\?.*/ => '?',
29
+ /\d+/ => '[0-9]',
30
+ /http:..[^\/]+/ => ''
31
+ }
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,70 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{plog}
8
+ s.version = "0.2.4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Kazuyoshi Tlacaelel"]
12
+ s.date = %q{2009-09-09}
13
+ s.default_executable = %q{plog}
14
+ s.description = %q{Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel}
15
+ s.email = %q{kazu.dev@gmail.com}
16
+ s.executables = ["plog"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/plog",
29
+ "lib/cli.rb",
30
+ "lib/completed_line.rb",
31
+ "lib/log_file.rb",
32
+ "lib/object_file.rb",
33
+ "lib/plog.rb",
34
+ "lib/url.rb",
35
+ "plog.gemspec",
36
+ "test/completed_line_test.rb",
37
+ "test/data/example.log",
38
+ "test/log_file_test.rb",
39
+ "test/object_file_test.rb",
40
+ "test/plog_test.rb",
41
+ "test/test_helper.rb",
42
+ "test/url_test.rb"
43
+ ]
44
+ s.homepage = %q{http://github.com/ktlacaelel/plog}
45
+ s.rdoc_options = ["--charset=UTF-8"]
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.3.3}
48
+ s.summary = %q{Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel}
49
+ s.test_files = [
50
+ "test/completed_line_test.rb",
51
+ "test/log_file_test.rb",
52
+ "test/object_file_test.rb",
53
+ "test/plog_test.rb",
54
+ "test/test_helper.rb",
55
+ "test/url_test.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
64
+ else
65
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
69
+ end
70
+ end
@@ -0,0 +1,156 @@
1
+ require 'test_helper'
2
+
3
+ class CompletedLineTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @valid_first_fragment = 'Completed in 5ms (View: 4, DB: 1) '
7
+ @valid_line = 'Completed in 5ms (View: 4, DB: 1) | 200 OK [http://www.example.com/users/12972]'
8
+ @valid_line_no_view = 'Completed in 5ms (DB: 5) | 200 OK [http://www.example.com/users/12972]'
9
+ @valid_second_fragment = ' 200 OK [http://www.example.com/users/12972]'
10
+
11
+ @valid_db_time = 1
12
+ @valid_view_time = 4
13
+ @valid_total_time = 5
14
+
15
+ @valid_db_time_no_view = 5
16
+ @valid_view_time_no_view = 0
17
+ @valid_total_time_no_view = 5
18
+
19
+ @valid_csv_line = '5,4,1,/users/[0-9]'
20
+ @valid_status_string = 'OK'
21
+ @valid_status_number = '200'
22
+ end
23
+
24
+ # ============================================================================
25
+ # READ
26
+ # ============================================================================
27
+
28
+ should 'read a valid completed line (standard)' do
29
+ Plog::CompletedLine.read! @valid_line
30
+ assert_equal @valid_view_time, Plog::CompletedLine.view_time
31
+ assert_equal @valid_db_time, Plog::CompletedLine.db_time
32
+ assert_equal @valid_total_time, Plog::CompletedLine.total_time
33
+ end
34
+
35
+ should 'read valid completed line (without view time)' do
36
+ Plog::CompletedLine.read! @valid_line_no_view
37
+ assert_equal 5, Plog::CompletedLine.total_time
38
+ assert_equal 0, Plog::CompletedLine.view_time
39
+ assert_equal 5, Plog::CompletedLine.db_time
40
+ end
41
+
42
+ # ============================================================================
43
+ # RECALCULATION
44
+ # ============================================================================
45
+
46
+ should 'recalculate acurately!' do
47
+ Plog::CompletedLine.read! @valid_line
48
+ Plog::CompletedLine.merge(1, 1)
49
+ assert_equal (@valid_view_time + 1), Plog::CompletedLine.view_time
50
+ assert_equal (@valid_db_time + 1), Plog::CompletedLine.db_time
51
+ assert_equal (@valid_total_time + 2), Plog::CompletedLine.total_time
52
+ end
53
+
54
+ # ============================================================================
55
+ # FRAGMENTATION
56
+ # ============================================================================
57
+
58
+ should 'fragmentize correctly' do
59
+ Plog::CompletedLine.read! @valid_line
60
+ first, second = Plog::CompletedLine.fragmentize!
61
+ assert_equal @valid_first_fragment, first
62
+ assert_equal @valid_second_fragment, second
63
+ end
64
+
65
+ # ============================================================================
66
+ # TIME RETRIVAL ( when view & db are *both* present )
67
+ # ============================================================================
68
+
69
+ should 'retrive correct total time (view + db)' do
70
+ Plog::CompletedLine.read! @valid_line
71
+ assert_equal @valid_total_time, Plog::CompletedLine.total_time
72
+ end
73
+
74
+ should 'retrive correct view time (view + db)' do
75
+ Plog::CompletedLine.read! @valid_line
76
+ assert_equal @valid_view_time, Plog::CompletedLine.view_time
77
+ end
78
+
79
+ should 'retrive correct db time (view + db)' do
80
+ Plog::CompletedLine.read! @valid_line
81
+ assert_equal @valid_db_time, Plog::CompletedLine.db_time
82
+ end
83
+
84
+ # ============================================================================
85
+ # TIME RETRIVAL ( when only db time is present )
86
+ # ============================================================================
87
+
88
+ should 'retrive correct total time (only db)' do
89
+ Plog::CompletedLine.read! @valid_line_no_view
90
+ assert_equal @valid_total_time_no_view, Plog::CompletedLine.total_time
91
+ end
92
+
93
+ should 'retrive correct view time (only db)' do
94
+ Plog::CompletedLine.read! @valid_line_no_view
95
+ assert_equal @valid_view_time_no_view, Plog::CompletedLine.view_time
96
+ end
97
+
98
+ should 'retrive correct db time (only db)' do
99
+ Plog::CompletedLine.read! @valid_line_no_view
100
+ assert_equal @valid_db_time_no_view, Plog::CompletedLine.db_time
101
+ end
102
+
103
+ # ============================================================================
104
+ # CSV COMPILATION
105
+ # ============================================================================
106
+
107
+ should 'export to csv' do
108
+ Plog::CompletedLine.read! @valid_line
109
+ assert_equal @valid_csv_line, Plog::CompletedLine.to_csv
110
+ end
111
+
112
+ # ============================================================================
113
+ # COMPLETED LINE DETECTION
114
+ # ============================================================================
115
+
116
+ should 'ignore *NON* completed lines' do
117
+ Plog::CompletedLine.read! 'completed'
118
+ assert_equal false, Plog::CompletedLine.completed_line?
119
+
120
+ Plog::CompletedLine.read! 'Completed'
121
+ assert_equal false, Plog::CompletedLine.completed_line?
122
+
123
+ Plog::CompletedLine.read! 'Completed Completed Completed Completed'
124
+ assert_equal false, Plog::CompletedLine.completed_line?
125
+
126
+ Plog::CompletedLine.read! 'aalsdjflasdjflsa dflksjdflkasjdf lfCompleted'
127
+ assert_equal false, Plog::CompletedLine.completed_line?
128
+
129
+ Plog::CompletedLine.read! nil
130
+ assert_equal false, Plog::CompletedLine.completed_line?
131
+
132
+ Plog::CompletedLine.read! false
133
+ assert_equal false, Plog::CompletedLine.completed_line?
134
+
135
+ Plog::CompletedLine.read! 0
136
+ assert_equal false, Plog::CompletedLine.completed_line?
137
+
138
+ Plog::CompletedLine.read! ''
139
+ assert_equal false, Plog::CompletedLine.completed_line?
140
+ end
141
+
142
+ # ============================================================================
143
+ # STATUS EXTRACTION
144
+ # ============================================================================
145
+
146
+ should 'extract status string' do
147
+ Plog::CompletedLine.read! @valid_line
148
+ assert_equal @valid_status_string, Plog::CompletedLine.status_string
149
+ end
150
+
151
+ should 'extract status number' do
152
+ Plog::CompletedLine.read! @valid_line
153
+ assert_equal @valid_status_number, Plog::CompletedLine.status_number
154
+ end
155
+
156
+ end
@@ -0,0 +1,10 @@
1
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
2
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
3
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
4
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
5
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
6
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
7
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
8
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
9
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
10
+ Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
@@ -0,0 +1,39 @@
1
+ require 'test_helper'
2
+
3
+ class LogFileTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @log_file = 'test/data/example.log'
7
+ @dump_dir = 'plog'
8
+ @file = Plog::LogFile.new(@log_file, @dump_dir)
9
+ @object_hash = 'a1e0e00d04e82bdf0f1ac151de03591c'
10
+ @subdir = 'plog/objects'
11
+ @object_path = @subdir + '/' + @object_hash
12
+ @final_csv_result = '10,70,40,30,/users/[0-9]'
13
+ @default_object_path = 'plog/objects/2a5565416b0c92c6c5081342322bf945'
14
+ end
15
+
16
+ def teardown
17
+ @file.close
18
+ end
19
+
20
+ should 'generate an appropriate object_path' do
21
+ assert_equal @default_object_path, @file.object_path
22
+ assert_equal @subdir, @file.objects_recipient_path
23
+ end
24
+
25
+ def whipe_out_objects_dir
26
+ Dir.glob('./plog/objects/*').each do |file|
27
+ FileUtils.rm_r(file, :force => true)
28
+ end
29
+ FileUtils.rmdir './plog/objects'
30
+ end
31
+
32
+ should 'parse data appropriately' do
33
+ whipe_out_objects_dir
34
+ @file = Plog::LogFile.new(@log_file, @dump_dir)
35
+ @file.parse_completed_lines!
36
+ assert_equal @final_csv_result, File.new(@object_path).read.chomp
37
+ end
38
+
39
+ end
@@ -0,0 +1,115 @@
1
+ require 'test_helper'
2
+
3
+ class ObjectFileTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @testing_filename = 'kazu.test'
7
+ @file = Plog::ObjectFile.new(@testing_filename, 'w+')
8
+ @default_serialized_data = '0,0,0,0,default'
9
+
10
+ @changed_hits = 1
11
+ @changed_total_time = 2
12
+ @changed_view_time = 3
13
+ @changed_db_time = 4
14
+ @changed_simplified_url = 'http://example.com'
15
+ @changed_serialized_data = '1,2,3,4,http://example.com'
16
+ @changed_serialized_data_twice = '2,4,6,8,http://example.com'
17
+
18
+ @export_data = '1 0.002 0.002 0.004 0.004 0.003 0.003 http://example.com '
19
+ end
20
+
21
+ def teardown
22
+ @file.close unless @skip_close
23
+ end
24
+
25
+ should 'load default values on initialization' do
26
+ assert_equal 'default', @file.simplified_url
27
+ assert_equal 0, @file.view_time
28
+ assert_equal 0, @file.db_time
29
+ assert_equal 0, @file.total_time
30
+ assert_equal 0, @file.total_hits
31
+ end
32
+
33
+ should 'serialize changes correctly' do
34
+ assert_equal @default_serialized_data, @file.serialize_changes
35
+ end
36
+
37
+ # ============================================================================
38
+ # DATA APPENDING
39
+ # ============================================================================
40
+
41
+ should 'increment view time correctly' do
42
+ @file.append_view_time 500
43
+ assert_equal 500, @file.view_time
44
+ @file.append_view_time 500
45
+ assert_equal 1000, @file.view_time
46
+ end
47
+
48
+ should 'increment db time correctly' do
49
+ @file.append_db_time 500
50
+ assert_equal 500, @file.db_time
51
+ @file.append_db_time 500
52
+ assert_equal 1000, @file.db_time
53
+ end
54
+
55
+ should 'increment total time correctly' do
56
+ @file.append_total_time 500
57
+ assert_equal 500, @file.total_time
58
+ @file.append_total_time 500
59
+ assert_equal 1000, @file.total_time
60
+ end
61
+
62
+ should 'increment hits count correctly' do
63
+ @file.append_hits 500
64
+ assert_equal 500, @file.total_hits
65
+ @file.append_hits 500
66
+ assert_equal 1000, @file.total_hits
67
+ end
68
+
69
+ # ============================================================================
70
+ # DATA STORAGE
71
+ # ============================================================================
72
+
73
+ should 'store default data correctly if nothing is given' do
74
+ @file.save_changes!
75
+ @file.close
76
+ @skip_close = true
77
+ stored_data = File.new(@testing_filename).read.chomp
78
+ assert_equal @default_serialized_data, stored_data
79
+ end
80
+
81
+ def alter_data
82
+ @file.append_hits @changed_hits
83
+ @file.append_db_time @changed_db_time
84
+ @file.append_view_time @changed_view_time
85
+ @file.append_total_time @changed_total_time
86
+ @file.simplified_url = @changed_simplified_url
87
+ end
88
+
89
+ # ============================================================================
90
+ # DATA LOADING
91
+ # ============================================================================
92
+
93
+ should 'store altered data & retrive correctly' do
94
+ alter_data
95
+ @file.save_changes!
96
+ @file.close
97
+ @skip_close = true
98
+
99
+ @file = Plog::ObjectFile.new(@testing_filename, 'w+')
100
+ assert_equal @changed_total_time, @file.total_time, 'total'
101
+ assert_equal @changed_view_time, @file.view_time, 'view'
102
+ assert_equal @changed_db_time, @file.db_time, 'db'
103
+ assert_equal @changed_hits, @file.total_hits, 'hits'
104
+ end
105
+
106
+ # ============================================================================
107
+ # DATA EXPORTS
108
+ # ============================================================================
109
+
110
+ should 'export data correctly' do
111
+ alter_data
112
+ assert_equal @export_data, @file.export
113
+ end
114
+
115
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+
3
+ class PlogTest < Test::Unit::TestCase
4
+
5
+ should 'do notin' do
6
+ end
7
+
8
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'plog'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ class URLTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @valid_line = 'Completed in 5ms (View: 4, DB: 1) | 200 OK [http://www.example.com/users/12972]'
7
+ @valid_url = 'http://www.example.com/users/12972'
8
+ @url = Plog::URL.new(@valid_url)
9
+ end
10
+
11
+ should 'return a valid url object when instantiated' do
12
+ assert_equal @valid_url, @url.to_s
13
+ end
14
+
15
+ should 'simplify correctly' do
16
+ assert_equal '/users/[0-9]', @url.simplify
17
+ end
18
+
19
+ should 'simplify removing arguments' do
20
+ @url = Plog::URL.new('http://www.example.com/users/1234?key1=val1&key2=val2')
21
+ assert_equal '/users/[0-9]?', @url.simplify
22
+ end
23
+
24
+ should 'hashify correctly' do
25
+ assert_equal '2a5565416b0c92c6c5081342322bf945', @url.hashify
26
+ end
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: plog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.4
5
+ platform: ruby
6
+ authors:
7
+ - Kazuyoshi Tlacaelel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-09 00:00:00 +09:00
13
+ default_executable: plog
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel
26
+ email: kazu.dev@gmail.com
27
+ executables:
28
+ - plog
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - bin/plog
42
+ - lib/cli.rb
43
+ - lib/completed_line.rb
44
+ - lib/log_file.rb
45
+ - lib/object_file.rb
46
+ - lib/plog.rb
47
+ - lib/url.rb
48
+ - plog.gemspec
49
+ - test/completed_line_test.rb
50
+ - test/data/example.log
51
+ - test/log_file_test.rb
52
+ - test/object_file_test.rb
53
+ - test/plog_test.rb
54
+ - test/test_helper.rb
55
+ - test/url_test.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/ktlacaelel/plog
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --charset=UTF-8
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.5
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel
84
+ test_files:
85
+ - test/completed_line_test.rb
86
+ - test/log_file_test.rb
87
+ - test/object_file_test.rb
88
+ - test/plog_test.rb
89
+ - test/test_helper.rb
90
+ - test/url_test.rb