Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea/
data.txt
result.json
data_large.txt
37 changes: 9 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
### Note
*Для работы скрипта требуется Ruby 2.4+*
# Task №2

# Задание №2
## What is done?

## Описание
В этом задании надо дооптимизировать задание 1й недели с использованием инструментов профилирования `CPU` и разъяснений, сделанных на 2й лекции.

### Что нужно сделать
- Построить и проанализировать отчёт `ruby-prof` в режиме `Flat`;
- Построить и проанализировать отчёт `ruby-prof` в режиме `Graph`;
- Построить и проанализировать отчёт `ruby-prof` в режиме `CallStack`;
- Построить и проанализировать отчёт `ruby-prof` в режиме `CallTree` c визуализацией в `QCachegrind`;
- Профилировать работающий процесс `rbspy`;
- Построить и проанализировать отчёт `flamegraph` с помощью `rbspy`;
- Добавить в программу `ProgressBar`;
- Научиться пользоваться `Valgrind massif` с `massif-visualizer`. Построить профиль использования памяти для итоговой версии вашей программы и добавить скриншот в `PR`;
- Написать хотя бы самый простой тест на производительность: `assert`, что время выполнения скрипта меньше значения, которое чуть больше реального, чтобы не давать ложных срабатываний;

### Главное
Нужно потренироваться методично работать по схеме с фидбек-лупом:
- построили отчёт каким-то из профилировщиков
- осознали его
- поняли, какая самая большая точка роста
- внесли минимальные изменения, чтобы использовать только эту точку роста
- перестроили отчёт, убедились, что проблема решена
- вычислили метрику - оценили, как изменение повлияло на метрику
- записали полученные результаты
- закоммитились
- перешли к следующей итерации
1. ruby-prof reports(alloc and wall) in Flat mode
2. ruby-prof report for QCacheGrind service
3. Flamegraph report by rbspy
4. Added ProgressBar
5. Added Massif Visualizer report in screenshot
![Before refactoring](/optimizations/step1/massif_visualizer.png)
6. Added regression functionality that stores previous best result and compare it with new result
32 changes: 32 additions & 0 deletions lib/_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'forwardable'
require_relative 'user'
require_relative 'report'

class Parser
@parsed_user = User.new

class << self
extend Forwardable

attr_reader :parsed_user

def parse_user(first_name, last_name)
@parsed_user.name = "#{first_name} #{last_name}"
end

def parse_session(browser, time, date)
@parsed_user.sessions_count += 1

@parsed_user.total_time += time
@parsed_user.longest_session = time if @parsed_user.longest_session < time
@parsed_user.browsers << browser
Report.unique_browsers << browser

@parsed_user.dates << date
end

private
def_delegator :@parsed_user, :name, :parsed_exists?
def_delegator :@parsed_user, :reset, :clear_cache
end
end
42 changes: 42 additions & 0 deletions lib/parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require_relative '_parser'
require 'pry-byebug'
require 'ruby-progressbar'

$support_dir = File.expand_path('../../spec/support', __FILE__ )
$optimizations_dir = File.expand_path('../../optimizations', __FILE__ )

def work(filename)
File.open("#{$support_dir}/result.json", 'w') do |f|
Report.prepare(f)

progress = ProgressBar.create(
title: 'Parsing',
total: File.size("#{$support_dir}/#{filename}"),
length: 80
)

IO.foreach("#{$support_dir}/#{filename}") do |cols|
row = cols.split(',')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно было бы ещё читать строку посимвольно, чтобы обойтись без split


if cols.start_with?('user')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот тут бы мы тогда при встрече с первой запятой посмотрели, что накопили в аккумуляторе и избежали start_with

if Parser.parsed_exists?
Report.add_parsed(Parser.parsed_user)

Parser.clear_cache
end

Parser.parse_user(row[2], row[3])
else
Parser.parse_session(row[3], row[4].to_i, row[5].strip)
end

progress.progress += cols.size
end

Report.add_parsed(Parser.parsed_user)

Report.add_analyse
end
end
49 changes: 49 additions & 0 deletions lib/report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require 'oj'
require 'set'
require 'json'

class Report
class << self
attr_reader :unique_browsers, :total_sessions, :total_users

def prepare(file)
@file = file
@unique_browsers = Set.new
@total_sessions = @total_users = 0

@file.write("{\"usersStats\":{")
end

def add_parsed(user)
browsers = user.prepare

@total_sessions += user.sessions_count
@total_users += 1

formatted = {
"sessionsCount": user.sessions_count,
"totalTime": "#{user.total_time} min.",
"longestSession": "#{user.longest_session} min.",
"browsers": browsers,
"usedIE": user.used_ie,
"alwaysUsedChrome": user.used_only_chrome,
"dates": user.dates
}

@file.write("\"#{user.name}\":#{Oj.dump(formatted, mode: :compat)},")
end

def add_analyse
analyze = {
"totalUsers": @total_users,
"uniqueBrowsersCount": @unique_browsers.size,
"totalSessions": @total_sessions,
"allBrowsers": @unique_browsers.sort.join(',').upcase!
}.to_json.tr!('{', '') << "}\n"

@file.write(analyze)
end
end
end
37 changes: 37 additions & 0 deletions lib/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

class User
attr_accessor :name, :sessions_count, :total_time, :longest_session,
:browsers, :dates, :used_ie, :used_only_chrome

def initialize
nullify

@dates = []
@browsers = []
end

def prepare
@dates.sort!.reverse!

browsers = @browsers.sort!.join(', ').upcase!

@used_only_chrome = true if browsers.end_with?('CHROME')
@used_ie = true if !@used_only_chrome && browsers.include?('INTERNET')

browsers
end

def reset
nullify

@dates.clear
@browsers.clear
end

private
def nullify
@longest_session = @total_time = @sessions_count = 0
@used_only_chrome = @used_ie = false
end
end
29 changes: 29 additions & 0 deletions optimizations/rubyprof.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require_relative '../lib/parser'
require_relative '../spec/stdout_to_file'
require 'ruby-prof'

GC.disable

save_stdout_to_file('rubyprof_wall.txt') do
RubyProf.measure_mode = RubyProf::WALL_TIME

result = RubyProf.profile do
work('data_65kb.txt')
end

printer = RubyProf::FlatPrinter.new(result)
printer.print($stdout)
end

GC.enable

save_stdout_to_file('rubyprof_alloc.txt') do
RubyProf.measure_mode = RubyProf::ALLOCATIONS

result = RubyProf.profile do
work('data_65kb.txt')
end

printer = RubyProf::FlatPrinter.new(result)
printer.print($stdout)
end
11 changes: 11 additions & 0 deletions optimizations/rubyprof_patched.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require_relative '../lib/parser'
require 'ruby-prof'

RubyProf.measure_mode = RubyProf::MEMORY

result = RubyProf.profile do
work('data_65kb.txt')
end

printer = RubyProf::CallTreePrinter.new(result)
printer.print(path: "#{$optimizations_dir}", profile: 'profile')
25 changes: 25 additions & 0 deletions optimizations/stackprof.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require_relative '../lib/parser'
require_relative '../spec/stdout_to_file'
require 'stackprof'

GC.disable

save_stdout_to_file('stackprof_wall.txt') do
profile_Data = StackProf.run(mode: :wall) do
work('data_65kb.txt')
end

StackProf::Report.new(profile_Data).print_text(nil, nil, nil, nil, nil, nil, $stdout)
StackProf::Report.new(profile_Data).print_method('Object#work', $stdout)
end

GC.enable

save_stdout_to_file('stackprof_object.txt') do
profile_Data = StackProf.run(mode: :object) do
work('data_65kb.txt')
end

StackProf::Report.new(profile_Data).print_text(nil, nil, nil, nil, nil, nil, $stdout)
StackProf::Report.new(profile_Data).print_method('Object#work', $stdout)
end
Loading