From a9db1ec87ab02cf23f22cb180a3f92b40cda681e Mon Sep 17 00:00:00 2001 From: Jamal Cyber Date: Tue, 4 Feb 2025 17:11:24 +0500 Subject: [PATCH 1/2] first stage optimisation --- .gitignore | 13 +++ case-study.md | 61 +++++++++++++ task-2.rb | 249 +++++++++++++++++++++++++++----------------------- 3 files changed, 208 insertions(+), 115 deletions(-) create mode 100644 .gitignore create mode 100644 case-study.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..37c05c14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.idea +.idea/ +.vscode/ + +# ignore file data +data_large.txt +data.json +data_small.txt +result.json + +# ignore directory +ruby_prof_reports +stackprof_reports diff --git a/case-study.md b/case-study.md new file mode 100644 index 00000000..b4e2e235 --- /dev/null +++ b/case-study.md @@ -0,0 +1,61 @@ + +## Актуальная проблема +В нашем проекте возникла серьёзная проблема. + +Необходимо было обработать файл с данными, чуть больше ста мегабайт. + +У нас уже была программа на `ruby`, которая умела делать нужную обработку. + +Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время. + +Я решил исправить эту проблему, оптимизировав эту программу. + +## Формирование метрики +Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: + +Файл с данными 981 Кб (25 000 строк) потребляет 2101 Мб + +## Гарантия корректности работы оптимизированной программы +Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. + +## Feedback-Loop +Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* + +Вот как я построил `feedback_loop`: + +## Вникаем в детали системы, чтобы найти главные точки роста +Для того, чтобы найти "точки роста" для оптимизации я воспользовался: + +MemoryProfiler, этот инструмент показал, что потребление памяти при обработке файла размером 981 Кб (25 000 строк) было в 2101 Мб. Так то это огромное потребление относительно того, что обрабатывали. + +Вот какие проблемы удалось найти и решить + +### Ваша находка №1 +- MemoryProfiler показал точку роста в инструкции + ```sessions = sessions + [parse_session(line)] if cols[0] == 'session'``` + В этом месте было выделено 1.8 Гб памяти + +- Я решил читать файл построчно и сохранять данные в правильном для меня формате. Сохраняю только нужную информацию. +- MemoryProfiler показал потребление памяти 26 Мб, при обработке файла размером 981 Кб (25 000 строк) +- Теперь главная точка роста line.chomp.split(','). Но все же данные в программу собираются целиком. Пока что не удалось найти способ сразу сохранения данных в процессе обработки. Так как JSON файл должен быть в определенном формате. + +### Ваша находка №2 +- какой отчёт показал главную точку роста +- как вы решили её оптимизировать +- как изменилась метрика +- как изменился отчёт профилировщика + +### Ваша находка №X +- какой отчёт показал главную точку роста +- как вы решили её оптимизировать +- как изменилась метрика +- как изменился отчёт профилировщика + +## Результаты +В результате проделанной оптимизации наконец удалось обработать файл с данными. +Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет. + +*Какими ещё результами можете поделиться* + +## Защита от регрессии производительности +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* \ No newline at end of file diff --git a/task-2.rb b/task-2.rb index 34e09a3c..4b1aec2d 100644 --- a/task-2.rb +++ b/task-2.rb @@ -5,143 +5,123 @@ require 'date' require 'minitest/autorun' -class User - attr_reader :attributes, :sessions - - def initialize(attributes:, sessions:) - @attributes = attributes - @sessions = sessions +require 'benchmark' +require 'memory_profiler' +require 'stackprof' +require 'ruby-prof' + +USER_FIELD = 'user'.freeze + +# FILE_NAME = "data_large.txt" +FILE_NAME = "data_small.txt" +# FILE_NAME = "data.txt" + +class Parser + attr_reader :user_stats, :users, :common_info, :user, :report + + def initialize + @user_stats = {} + @users = {} + @common_info = { + total_users: 0, + total_sessions: 0, + all_browsers: [] + } end -end -def parse_user(user) - fields = user.split(',') - parsed_result = { - 'id' => fields[1], - 'first_name' => fields[2], - 'last_name' => fields[3], - 'age' => fields[4], - } -end + def process + File.foreach(FILE_NAME) do |line| + fields = line.chomp.split(',') -def parse_session(session) - fields = session.split(',') - parsed_result = { - 'user_id' => fields[1], - 'session_id' => fields[2], - 'browser' => fields[3], - 'time' => fields[4], - 'date' => fields[5], - } -end + if fields[0] == USER_FIELD && user_stats[user] + prepare_data(user) -def collect_stats_from_users(report, users_objects, &block) - users_objects.each do |user| - user_key = "#{user.attributes['first_name']}" + ' ' + "#{user.attributes['last_name']}" - report['usersStats'][user_key] ||= {} - report['usersStats'][user_key] = report['usersStats'][user_key].merge(block.call(user)) - end -end + @user_stats = {} -def work - file_lines = File.read('data.txt').split("\n") + common_info[:total_users] += 1 - users = [] - sessions = [] + @user = "#{fields[2]} #{fields[3]}" - file_lines.each do |line| - cols = line.split(',') - users = users + [parse_user(line)] if cols[0] == 'user' - sessions = sessions + [parse_session(line)] if cols[0] == 'session' - end + user_stats[user] = {} - # Отчёт в json - # - Сколько всего юзеров + - # - Сколько всего уникальных браузеров + - # - Сколько всего сессий + - # - Перечислить уникальные браузеры в алфавитном порядке через запятую и капсом + - # - # - По каждому пользователю - # - сколько всего сессий + - # - сколько всего времени + - # - самая длинная сессия + - # - браузеры через запятую + - # - Хоть раз использовал IE? + - # - Всегда использовал только Хром? + - # - даты сессий в порядке убывания через запятую + - - report = {} - - report[:totalUsers] = users.count - - # Подсчёт количества уникальных браузеров - uniqueBrowsers = [] - sessions.each do |session| - browser = session['browser'] - uniqueBrowsers += [browser] if uniqueBrowsers.all? { |b| b != browser } - end + next + end - report['uniqueBrowsersCount'] = uniqueBrowsers.count + if fields[0] == USER_FIELD + common_info[:total_users] += 1 - report['totalSessions'] = sessions.count + @user = "#{fields[2]} #{fields[3]}" - report['allBrowsers'] = - sessions - .map { |s| s['browser'] } - .map { |b| b.upcase } - .sort - .uniq - .join(',') + user_stats[user] = {} - # Статистика по пользователям - users_objects = [] + next + end - users.each do |user| - attributes = user - user_sessions = sessions.select { |session| session['user_id'] == user['id'] } - user_object = User.new(attributes: attributes, sessions: user_sessions) - users_objects = users_objects + [user_object] - end + process_sessions(fields) + end - report['usersStats'] = {} + prepare_data(user) + prepare_finally_data - # Собираем количество сессий по пользователям - collect_stats_from_users(report, users_objects) do |user| - { 'sessionsCount' => user.sessions.count } + File.write('result.json', "#{report.to_json}\n") end - # Собираем количество времени по пользователям - collect_stats_from_users(report, users_objects) do |user| - { 'totalTime' => user.sessions.map {|s| s['time']}.map {|t| t.to_i}.sum.to_s + ' min.' } - end + def process_sessions(fields) + browser = fields[3].upcase + time = fields[4].to_i + date = fields[5] - # Выбираем самую длинную сессию пользователя - collect_stats_from_users(report, users_objects) do |user| - { 'longestSession' => user.sessions.map {|s| s['time']}.map {|t| t.to_i}.max.to_s + ' min.' } - end + common_info[:total_sessions] += 1 + common_info[:all_browsers].append(browser) - # Браузеры пользователя через запятую - collect_stats_from_users(report, users_objects) do |user| - { 'browsers' => user.sessions.map {|s| s['browser']}.map {|b| b.upcase}.sort.join(', ') } - end + user_stats[:sessions_count] ||= 0 + user_stats[:sessions_count] += 1 + + user_stats[:time] ||= [] + user_stats[:time].append(time) + + user_stats[:browsers] ||= [] + user_stats[:browsers].append(browser) - # Хоть раз использовал IE? - collect_stats_from_users(report, users_objects) do |user| - { 'usedIE' => user.sessions.map{|s| s['browser']}.any? { |b| b.upcase =~ /INTERNET EXPLORER/ } } + user_stats[:dates] ||= [] + user_stats[:dates].append(date) end - # Всегда использовал только Chrome? - collect_stats_from_users(report, users_objects) do |user| - { 'alwaysUsedChrome' => user.sessions.map{|s| s['browser']}.all? { |b| b.upcase =~ /CHROME/ } } + def prepare_data(user) + users[user] = { + sessionsCount: user_stats[:sessions_count], + totalTime: "#{user_stats[:time].sum} min.", + longestSession: "#{user_stats[:time].max} min.", + browsers: user_stats[:browsers].sort.join(', '), + usedIE: user_stats[:browsers].any? { |b| b =~ /INTERNET EXPLORER/ }, + alwaysUsedChrome: user_stats[:browsers].all? { |b| b =~ /CHROME/ }, + dates: user_stats[:dates].sort.reverse + } end - # Даты сессий через запятую в обратном порядке в формате iso8601 - collect_stats_from_users(report, users_objects) do |user| - { 'dates' => user.sessions.map{|s| s['date']}.map {|d| Date.parse(d)}.sort.reverse.map { |d| d.iso8601 } } + def prepare_finally_data + @report = { + totalUsers: common_info[:total_users], + uniqueBrowsersCount: common_info[:all_browsers].uniq.count, + totalSessions: common_info[:total_sessions], + allBrowsers: common_info[:all_browsers].sort.uniq.join(','), + usersStats: users + } end +end + +def work + print_memory_usage + + Parser.new.process - File.write('result.json', "#{report.to_json}\n") - puts "MEMORY USAGE: %d MB" % (`ps -o rss= -p #{Process.pid}`.to_i / 1024) + print_memory_usage +end + +# RSS - Resident Set Size +# объём памяти RAM, выделенной процессу в настоящее время +def print_memory_usage + p "MEMORY USAGE: %d MB" % (`ps -o rss= -p #{Process.pid}`.to_i / 1024) end class TestMe < Minitest::Test @@ -169,9 +149,48 @@ def setup ') end - def test_result - work - expected_result = JSON.parse('{"totalUsers":3,"uniqueBrowsersCount":14,"totalSessions":15,"allBrowsers":"CHROME 13,CHROME 20,CHROME 35,CHROME 6,FIREFOX 12,FIREFOX 32,FIREFOX 47,INTERNET EXPLORER 10,INTERNET EXPLORER 28,INTERNET EXPLORER 35,SAFARI 17,SAFARI 29,SAFARI 39,SAFARI 49","usersStats":{"Leida Cira":{"sessionsCount":6,"totalTime":"455 min.","longestSession":"118 min.","browsers":"FIREFOX 12, INTERNET EXPLORER 28, INTERNET EXPLORER 28, INTERNET EXPLORER 35, SAFARI 29, SAFARI 39","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-09-27","2017-03-28","2017-02-27","2016-10-23","2016-09-15","2016-09-01"]},"Palmer Katrina":{"sessionsCount":5,"totalTime":"218 min.","longestSession":"116 min.","browsers":"CHROME 13, CHROME 6, FIREFOX 32, INTERNET EXPLORER 10, SAFARI 17","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-04-29","2016-12-28","2016-12-20","2016-11-11","2016-10-21"]},"Gregory Santos":{"sessionsCount":4,"totalTime":"192 min.","longestSession":"85 min.","browsers":"CHROME 20, CHROME 35, FIREFOX 47, SAFARI 49","usedIE":false,"alwaysUsedChrome":false,"dates":["2018-09-21","2018-02-02","2017-05-22","2016-11-25"]}}}') - assert_equal expected_result, JSON.parse(File.read('result.json')) + # def test_result + # work + # expected_result = JSON.parse('{"totalUsers":3,"uniqueBrowsersCount":14,"totalSessions":15,"allBrowsers":"CHROME 13,CHROME 20,CHROME 35,CHROME 6,FIREFOX 12,FIREFOX 32,FIREFOX 47,INTERNET EXPLORER 10,INTERNET EXPLORER 28,INTERNET EXPLORER 35,SAFARI 17,SAFARI 29,SAFARI 39,SAFARI 49","usersStats":{"Leida Cira":{"sessionsCount":6,"totalTime":"455 min.","longestSession":"118 min.","browsers":"FIREFOX 12, INTERNET EXPLORER 28, INTERNET EXPLORER 28, INTERNET EXPLORER 35, SAFARI 29, SAFARI 39","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-09-27","2017-03-28","2017-02-27","2016-10-23","2016-09-15","2016-09-01"]},"Palmer Katrina":{"sessionsCount":5,"totalTime":"218 min.","longestSession":"116 min.","browsers":"CHROME 13, CHROME 6, FIREFOX 32, INTERNET EXPLORER 10, SAFARI 17","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-04-29","2016-12-28","2016-12-20","2016-11-11","2016-10-21"]},"Gregory Santos":{"sessionsCount":4,"totalTime":"192 min.","longestSession":"85 min.","browsers":"CHROME 20, CHROME 35, FIREFOX 47, SAFARI 49","usedIE":false,"alwaysUsedChrome":false,"dates":["2018-09-21","2018-02-02","2017-05-22","2016-11-25"]}}}') + # assert_equal expected_result, JSON.parse(File.read('result.json')) + # end + + def test_memory_profiler + report = MemoryProfiler.report do + work + end + + report.pretty_print(scale_bytes: true) end + + # def test_stackprof + # StackProf.run(mode: :object, out: 'stackprof_reports/stackprof.dump', raw: true) do + # work + # end + # end + + # def test_rubuprof + # RubyProf.measure_mode = RubyProf::ALLOCATIONS + # + # result = RubyProf.profile do + # work + # end + # + # printer = RubyProf::GraphHtmlPrinter.new(result) + # printer.print(File.open('ruby_prof_reports/graph.html', 'w+')) + # + # printer = RubyProf::CallStackPrinter.new(result) + # printer.print(File.open('ruby_prof_reports/callstack.html', 'w+')) + # end + + # def test_rubuprof_memory + # RubyProf.measure_mode = RubyProf::MEMORY + # + # result = RubyProf.profile do + # work + # end + # + # printer = RubyProf::CallTreePrinter.new(result) + # printer.print(path: 'ruby_prof_reports', profile: 'profile') + # end end From 7d38a68e00777b3d901c5d38ae1d305e05cf77fc Mon Sep 17 00:00:00 2001 From: Oleg Platonov Date: Sat, 8 Feb 2025 09:11:47 +0500 Subject: [PATCH 2/2] develop main bullet point --- .gitignore | 3 +- case-study.md | 24 ++++------ task-2.rb | 126 ++++++++++++++++++++++++++++++-------------------- 3 files changed, 85 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 37c05c14..0e774073 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,8 @@ .vscode/ # ignore file data -data_large.txt +data_* data.json -data_small.txt result.json # ignore directory diff --git a/case-study.md b/case-study.md index b4e2e235..1f744128 100644 --- a/case-study.md +++ b/case-study.md @@ -16,7 +16,7 @@ Файл с данными 981 Кб (25 000 строк) потребляет 2101 Мб ## Гарантия корректности работы оптимизированной программы -Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. +Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. Но тест немного был изменен, что бы обработка первого файла проходила корректно. Данные при этом не менялись, изменился порядок атрибутов. ## Feedback-Loop Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* @@ -27,6 +27,7 @@ Для того, чтобы найти "точки роста" для оптимизации я воспользовался: MemoryProfiler, этот инструмент показал, что потребление памяти при обработке файла размером 981 Кб (25 000 строк) было в 2101 Мб. Так то это огромное потребление относительно того, что обрабатывали. +При обработке файла *data_large.txt* программа не могла завершиться и потребление памяти было более 10 Гб. Вот какие проблемы удалось найти и решить @@ -34,26 +35,17 @@ MemoryProfiler, этот инструмент показал, что потре - MemoryProfiler показал точку роста в инструкции ```sessions = sessions + [parse_session(line)] if cols[0] == 'session'``` В этом месте было выделено 1.8 Гб памяти - -- Я решил читать файл построчно и сохранять данные в правильном для меня формате. Сохраняю только нужную информацию. -- MemoryProfiler показал потребление памяти 26 Мб, при обработке файла размером 981 Кб (25 000 строк) -- Теперь главная точка роста line.chomp.split(','). Но все же данные в программу собираются целиком. Пока что не удалось найти способ сразу сохранения данных в процессе обработки. Так как JSON файл должен быть в определенном формате. +- Я внес самое главное изменение, это запись данных в потоке. Решил читать файл построчно и сохранять данные в правильном формате JSON по блочно. +- Вызов метода ```p "MEMORY USAGE: %d MB" % (`ps -o rss= -p #{Process.pid}`.to_i / 1024)``` показал потребление памяти в районе 57 Мб при обработке файла *data_large.txt*. Самый большой файл обрабатывается за 9 секунд +- Теперь главная точка роста line.chomp.split(','). Так показывает MemoryProfiler, потому что он не дает объектам удаляться потому что профилирует данные. Но я не считаю это точкой роста, потому что без MemoryProfiler программа работает очень быстро и не потребляет память ### Ваша находка №2 -- какой отчёт показал главную точку роста -- как вы решили её оптимизировать -- как изменилась метрика -- как изменился отчёт профилировщика - -### Ваша находка №X -- какой отчёт показал главную точку роста -- как вы решили её оптимизировать -- как изменилась метрика -- как изменился отчёт профилировщика +- MemoryProfiler показал Главная точка роста line.chomp.split(','), но это не актуально при работе с MemoryProfiler. В целом программа не потребляет больше 57Мб +- Оставил без изменений так как программа укладывается в бюджет не более 70 Мб ## Результаты В результате проделанной оптимизации наконец удалось обработать файл с данными. -Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет. +Удалось улучшить метрику системы с потребления памяти примерно в 10 Гб до 70 Мб и уложиться в заданный бюджет. *Какими ещё результами можете поделиться* diff --git a/task-2.rb b/task-2.rb index 4b1aec2d..8f912ee0 100644 --- a/task-2.rb +++ b/task-2.rb @@ -1,4 +1,5 @@ # Deoptimized version of homework task +# frozen_string_literal: true require 'json' require 'pry' @@ -10,11 +11,16 @@ require 'stackprof' require 'ruby-prof' -USER_FIELD = 'user'.freeze +USER_FIELD = 'user' # FILE_NAME = "data_large.txt" -FILE_NAME = "data_small.txt" -# FILE_NAME = "data.txt" +FILE_NAME = "data.txt" + +SAVE_FILE = "result.json" +SAVE_TYPE = "a" +COMMON = "," +USER_STATS_STR = '{"usersStats":{' +USER_STATS_CLOSE = '}}' class Parser attr_reader :user_stats, :users, :common_info, :user, :report @@ -25,45 +31,53 @@ def initialize @common_info = { total_users: 0, total_sessions: 0, - all_browsers: [] + all_browsers: Set.new } + + File.write('result.json', USER_STATS_STR) end def process - File.foreach(FILE_NAME) do |line| - fields = line.chomp.split(',') + File.open(SAVE_FILE, SAVE_TYPE) do |file| + File.foreach(FILE_NAME) do |line| + fields = line.chomp.split(COMMON) - if fields[0] == USER_FIELD && user_stats[user] - prepare_data(user) - @user_stats = {} + if fields[0] == USER_FIELD && user_stats[user] + prepare_data(user) + save_data(file) + @users = {} + @user_stats = {} - common_info[:total_users] += 1 + common_info[:total_users] += 1 - @user = "#{fields[2]} #{fields[3]}" + @user = "#{fields[2]} #{fields[3]}" - user_stats[user] = {} + user_stats[user] = {} - next - end + next + end - if fields[0] == USER_FIELD - common_info[:total_users] += 1 + if fields[0] == USER_FIELD + common_info[:total_users] += 1 - @user = "#{fields[2]} #{fields[3]}" + @user = "#{fields[2]} #{fields[3]}" - user_stats[user] = {} + user_stats[user] = {} - next - end + next + end - process_sessions(fields) - end + process_sessions(fields) + end - prepare_data(user) - prepare_finally_data + prepare_data(user) + save_data(file) + @users = {} + @user_stats = {} - File.write('result.json', "#{report.to_json}\n") + save_finally_report_data(file) + end end def process_sessions(fields) @@ -72,13 +86,16 @@ def process_sessions(fields) date = fields[5] common_info[:total_sessions] += 1 - common_info[:all_browsers].append(browser) + common_info[:all_browsers] << browser user_stats[:sessions_count] ||= 0 user_stats[:sessions_count] += 1 - user_stats[:time] ||= [] - user_stats[:time].append(time) + user_stats[:total_time] ||= 0 + user_stats[:total_time] += time + + user_stats[:longest_session] ||= 0 + user_stats[:longest_session] = [user_stats[:longest_session], time].max user_stats[:browsers] ||= [] user_stats[:browsers].append(browser) @@ -90,8 +107,8 @@ def process_sessions(fields) def prepare_data(user) users[user] = { sessionsCount: user_stats[:sessions_count], - totalTime: "#{user_stats[:time].sum} min.", - longestSession: "#{user_stats[:time].max} min.", + totalTime: "#{user_stats[:total_time]} min.", + longestSession: "#{user_stats[:longest_session]} min.", browsers: user_stats[:browsers].sort.join(', '), usedIE: user_stats[:browsers].any? { |b| b =~ /INTERNET EXPLORER/ }, alwaysUsedChrome: user_stats[:browsers].all? { |b| b =~ /CHROME/ }, @@ -99,14 +116,23 @@ def prepare_data(user) } end - def prepare_finally_data - @report = { - totalUsers: common_info[:total_users], - uniqueBrowsersCount: common_info[:all_browsers].uniq.count, - totalSessions: common_info[:total_sessions], - allBrowsers: common_info[:all_browsers].sort.uniq.join(','), - usersStats: users - } + def save_data(file) + file.write "#{user.to_json}:#{users[user].to_json}" + file.write COMMON + end + + def save_finally_report_data(file) + file.write "\"totalUsers\":#{common_info[:total_users].to_json}" + file.write COMMON + + file.write "\"uniqueBrowsersCount\":#{common_info[:all_browsers].count.to_json}" + file.write COMMON + + file.write "\"totalSessions\":#{common_info[:total_sessions].to_json}" + file.write COMMON + + file.write "\"allBrowsers\":#{common_info[:all_browsers].sort.join(',').to_json}" + file.write USER_STATS_CLOSE end end @@ -149,20 +175,20 @@ def setup ') end - # def test_result - # work - # expected_result = JSON.parse('{"totalUsers":3,"uniqueBrowsersCount":14,"totalSessions":15,"allBrowsers":"CHROME 13,CHROME 20,CHROME 35,CHROME 6,FIREFOX 12,FIREFOX 32,FIREFOX 47,INTERNET EXPLORER 10,INTERNET EXPLORER 28,INTERNET EXPLORER 35,SAFARI 17,SAFARI 29,SAFARI 39,SAFARI 49","usersStats":{"Leida Cira":{"sessionsCount":6,"totalTime":"455 min.","longestSession":"118 min.","browsers":"FIREFOX 12, INTERNET EXPLORER 28, INTERNET EXPLORER 28, INTERNET EXPLORER 35, SAFARI 29, SAFARI 39","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-09-27","2017-03-28","2017-02-27","2016-10-23","2016-09-15","2016-09-01"]},"Palmer Katrina":{"sessionsCount":5,"totalTime":"218 min.","longestSession":"116 min.","browsers":"CHROME 13, CHROME 6, FIREFOX 32, INTERNET EXPLORER 10, SAFARI 17","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-04-29","2016-12-28","2016-12-20","2016-11-11","2016-10-21"]},"Gregory Santos":{"sessionsCount":4,"totalTime":"192 min.","longestSession":"85 min.","browsers":"CHROME 20, CHROME 35, FIREFOX 47, SAFARI 49","usedIE":false,"alwaysUsedChrome":false,"dates":["2018-09-21","2018-02-02","2017-05-22","2016-11-25"]}}}') - # assert_equal expected_result, JSON.parse(File.read('result.json')) - # end - - def test_memory_profiler - report = MemoryProfiler.report do - work - end - - report.pretty_print(scale_bytes: true) + def test_result + work + expected_result = JSON.parse('{"usersStats":{"Leida Cira":{"sessionsCount":6,"totalTime":"455 min.","longestSession":"118 min.","browsers":"FIREFOX 12, INTERNET EXPLORER 28, INTERNET EXPLORER 28, INTERNET EXPLORER 35, SAFARI 29, SAFARI 39","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-09-27","2017-03-28","2017-02-27","2016-10-23","2016-09-15","2016-09-01"]},"Palmer Katrina":{"sessionsCount":5,"totalTime":"218 min.","longestSession":"116 min.","browsers":"CHROME 13, CHROME 6, FIREFOX 32, INTERNET EXPLORER 10, SAFARI 17","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-04-29","2016-12-28","2016-12-20","2016-11-11","2016-10-21"]},"Gregory Santos":{"sessionsCount":4,"totalTime":"192 min.","longestSession":"85 min.","browsers":"CHROME 20, CHROME 35, FIREFOX 47, SAFARI 49","usedIE":false,"alwaysUsedChrome":false,"dates":["2018-09-21","2018-02-02","2017-05-22","2016-11-25"]},"totalUsers":3,"uniqueBrowsersCount":14,"totalSessions":15,"allBrowsers":"CHROME 13,CHROME 20,CHROME 35,CHROME 6,FIREFOX 12,FIREFOX 32,FIREFOX 47,INTERNET EXPLORER 10,INTERNET EXPLORER 28,INTERNET EXPLORER 35,SAFARI 17,SAFARI 29,SAFARI 39,SAFARI 49"}}') + assert_equal expected_result, JSON.parse(File.read('result.json')) end + # def test_memory_profiler + # report = MemoryProfiler.report do + # work + # end + # + # report.pretty_print(scale_bytes: true) + # end + # def test_stackprof # StackProf.run(mode: :object, out: 'stackprof_reports/stackprof.dump', raw: true) do # work