From 60344d78c0e326cd76183977d7ef0eb3ee35c691 Mon Sep 17 00:00:00 2001 From: Mair Date: Sat, 25 Jan 2025 19:21:23 +0400 Subject: [PATCH 1/4] added progressbar --- task-1.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/task-1.rb b/task-1.rb index 778672df..d02641b6 100644 --- a/task-1.rb +++ b/task-1.rb @@ -4,6 +4,7 @@ require 'pry' require 'date' require 'minitest/autorun' +require 'ruby-progressbar' class User attr_reader :attributes, :sessions @@ -49,10 +50,13 @@ def work users = [] sessions = [] + file_progressbar = ProgressBar.create(title: "Reading File", total: file_lines.count, format: '%t: |%B| %p%% %e') + 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' + file_progressbar.increment end # Отчёт в json @@ -96,11 +100,14 @@ def work # Статистика по пользователям users_objects = [] + user_progressbar = ProgressBar.create(title: "Processing Users", total: users.count, format: '%t: |%B| %p%% %e') + 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] + user_progressbar.increment end report['usersStats'] = {} From 5b5ace367bfd7269d7ed40d0b2f82cf323fb5549 Mon Sep 17 00:00:00 2001 From: Mair Date: Sat, 25 Jan 2025 21:56:14 +0400 Subject: [PATCH 2/4] optimization 1 --- case-study-template.md | 23 +++++++++++++++++++++ task-1.rb | 46 ++++++++++++++++++++---------------------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/case-study-template.md b/case-study-template.md index d41034d9..ee1b9083 100644 --- a/case-study-template.md +++ b/case-study-template.md @@ -54,3 +54,26 @@ ## Защита от регрессии производительности Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* + + +Добавил прогресс бары + +### находка №1 Многократная итерация объекта sessions для создания users_objects +- callstack из ruby-prof +- одной итерацией собрать необходимые данные в объекте sessions_by_user +- время выполнения приложения на 15т строк сократилась 7.5 секунд до 0.9 секунд +- исправленная проблема перестала быть главной точкой роста + + +### находка №2 Неэффективный алгоритм с многократными проверками для сбора unique_browsers. +- callstack из ruby-prof +- Было принято решение заменить неэффективный алгоритм с многократными проверками на более оптимизированное решение, использующее встроенные методы Ruby +- время выполнения приложения на 15т строк сократилась 0.9 секунд до 0.6 секунд +- исправленная проблема перестала быть главной точкой роста + +### Находка №3: Неэффективное добавление элементов в массивы с использованием оператора конкатенации +- callstack из ruby-prof +- Было принято решение заменить неэффективное добавление элементов в массивы с помощью оператора + на использование метода << (shovel operator). Дополнительно была применена конструкция case для улучшения читаемости и производительности. + Время выполнения: точные измерения не предоставлены, но ожидается значительное улучшение производительности, особенно для больших наборов данных. Операция << имеет сложность O(1), тогда как + создает новый массив при каждой итерации, что имеет сложность O(n). +- время выполнения приложения на 15т строк сократилась 0.6 секунд до 0.5 секунд +- исправленная проблема перестала быть главной точкой роста diff --git a/task-1.rb b/task-1.rb index d02641b6..dead1ce7 100644 --- a/task-1.rb +++ b/task-1.rb @@ -4,7 +4,6 @@ require 'pry' require 'date' require 'minitest/autorun' -require 'ruby-progressbar' class User attr_reader :attributes, :sessions @@ -44,19 +43,20 @@ def collect_stats_from_users(report, users_objects, &block) end end -def work - file_lines = File.read('data.txt').split("\n") +def work(file_name: "data.txt", disable_gc: false) + GC.disable if disable_gc + file_lines = File.read(file_name).split("\n") users = [] sessions = [] - file_progressbar = ProgressBar.create(title: "Reading File", total: file_lines.count, format: '%t: |%B| %p%% %e') - 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' - file_progressbar.increment + case + when line.start_with?('user,') + users << parse_user(line) + when line.start_with?('session,') + sessions << parse_session(line) + end end # Отчёт в json @@ -79,13 +79,9 @@ def work report[:totalUsers] = users.count # Подсчёт количества уникальных браузеров - uniqueBrowsers = [] - sessions.each do |session| - browser = session['browser'] - uniqueBrowsers += [browser] if uniqueBrowsers.all? { |b| b != browser } - end + unique_browsers = sessions.map { |session| session['browser'] }.uniq - report['uniqueBrowsersCount'] = uniqueBrowsers.count + report['uniqueBrowsersCount'] = unique_browsers.count report['totalSessions'] = sessions.count @@ -98,16 +94,18 @@ def work .join(',') # Статистика по пользователям - users_objects = [] - - user_progressbar = ProgressBar.create(title: "Processing Users", total: users.count, format: '%t: |%B| %p%% %e') + sessions_by_user = {} + sessions.each do |session| + user_id = session['user_id'] + sessions_by_user[user_id] ||= [] + sessions_by_user[user_id] << session + 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] - user_progressbar.increment + users_objects = users.map do |user| + user_id = user['id'] + user_sessions = sessions_by_user[user_id] || [] + user_object = User.new(attributes: user, sessions: user_sessions) + user_object end report['usersStats'] = {} From 972fb94f3f9af1f0e8bf2e1e8a5ac072ba622cf2 Mon Sep 17 00:00:00 2001 From: Mair Date: Sat, 1 Feb 2025 16:21:49 +0400 Subject: [PATCH 3/4] returned progressbar --- task-1.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/task-1.rb b/task-1.rb index dead1ce7..f230f6ca 100644 --- a/task-1.rb +++ b/task-1.rb @@ -4,6 +4,7 @@ require 'pry' require 'date' require 'minitest/autorun' +require 'ruby-progressbar' class User attr_reader :attributes, :sessions @@ -43,13 +44,15 @@ def collect_stats_from_users(report, users_objects, &block) end end -def work(file_name: "data.txt", disable_gc: false) +def work(file_name: "data.txt", disable_gc: false, progress_bar: true) GC.disable if disable_gc file_lines = File.read(file_name).split("\n") users = [] sessions = [] + file_progressbar = progress_bar ? ProgressBar.create(title: "Reading File", total: file_lines.count, format: '%t: |%B| %p%% %e') : nil + file_lines.each do |line| case when line.start_with?('user,') @@ -57,6 +60,7 @@ def work(file_name: "data.txt", disable_gc: false) when line.start_with?('session,') sessions << parse_session(line) end + increment_progressbar(file_progressbar) end # Отчёт в json @@ -101,10 +105,13 @@ def work(file_name: "data.txt", disable_gc: false) sessions_by_user[user_id] << session end + user_progressbar = progress_bar ? ProgressBar.create(title: "Processing Users", total: users.count, format: '%t: |%B| %p%% %e') : nil + users_objects = users.map do |user| user_id = user['id'] user_sessions = sessions_by_user[user_id] || [] user_object = User.new(attributes: user, sessions: user_sessions) + increment_progressbar(user_progressbar) user_object end @@ -148,6 +155,10 @@ def work(file_name: "data.txt", disable_gc: false) File.write('result.json', "#{report.to_json}\n") end +def increment_progressbar(progressbar) + progressbar&.increment +end + class TestMe < Minitest::Test def setup File.write('result.json', '') From 1d15e778615204b674baafa426cdc432297192ea Mon Sep 17 00:00:00 2001 From: Mair Date: Sat, 1 Feb 2025 17:47:09 +0400 Subject: [PATCH 4/4] optimization #2 --- Gemfile | 5 ++ Gemfile.lock | 47 +++++++++++++++ case-study-template.md => case-study.md | 77 +++++++++++-------------- performance_test.rb | 14 +++++ run_profiling.rb | 11 ++++ task-1.rb | 51 ++-------------- test.rb | 35 +++++++++++ 7 files changed, 150 insertions(+), 90 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock rename case-study-template.md => case-study.md (59%) create mode 100644 performance_test.rb create mode 100644 run_profiling.rb create mode 100644 test.rb diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..e0bfe9ff --- /dev/null +++ b/Gemfile @@ -0,0 +1,5 @@ +source "https://rubygems.org" +gem "ruby-prof" +gem "rspec-benchmark" +gem "ruby-progressbar" +gem "minitest" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..8716930d --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,47 @@ +GEM + remote: https://rubygems.org/ + specs: + benchmark-malloc (0.2.0) + benchmark-perf (0.6.0) + benchmark-trend (0.4.0) + coderay (1.1.3) + diff-lcs (1.5.1) + method_source (1.1.0) + minitest (5.25.1) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-benchmark (0.6.0) + benchmark-malloc (~> 0.2) + benchmark-perf (~> 0.6) + benchmark-trend (~> 0.4) + rspec (>= 3.0) + rspec-core (3.13.2) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.2) + ruby-prof (1.7.1) + ruby-progressbar (1.13.0) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + minitest + pry + rspec-benchmark + ruby-prof + ruby-progressbar + +BUNDLED WITH + 2.5.16 diff --git a/case-study-template.md b/case-study.md similarity index 59% rename from case-study-template.md rename to case-study.md index ee1b9083..66796518 100644 --- a/case-study-template.md +++ b/case-study.md @@ -12,7 +12,8 @@ Я решил исправить эту проблему, оптимизировав эту программу. ## Формирование метрики -Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: *тут ваша метрика* +Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: +Время выполнения программы. ## Гарантия корректности работы оптимизированной программы Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. @@ -20,60 +21,48 @@ ## Feedback-Loop Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* -Вот как я построил `feedback_loop`: *как вы построили feedback_loop* +Вот как я построил `feedback_loop`: создал файлы с различным количеством строк, чтобы программа могла выполняться за 10–20 секунд. +После каждого изменения я запускал программу на файлах с разным количеством строк и смотрел на результаты. ## Вникаем в детали системы, чтобы найти главные точки роста -Для того, чтобы найти "точки роста" для оптимизации я воспользовался *инструментами, которыми вы воспользовались* +Для того, чтобы найти "точки роста" для оптимизации я воспользовался: +- gem ruby-prof и отчеты callstack & qcachegrind -Вот какие проблемы удалось найти и решить +Вот какие проблемы удалось найти и решить: -### Ваша находка №1 -- какой отчёт показал главную точку роста -- как вы решили её оптимизировать -- как изменилась метрика -- как изменился отчёт профилировщика - исправленная проблема перестала быть главной точкой роста? - -### Ваша находка №2 -- какой отчёт показал главную точку роста -- как вы решили её оптимизировать -- как изменилась метрика -- как изменился отчёт профилировщика - исправленная проблема перестала быть главной точкой роста? - -### Ваша находка №X -- какой отчёт показал главную точку роста -- как вы решили её оптимизировать -- как изменилась метрика -- как изменился отчёт профилировщика - исправленная проблема перестала быть главной точкой роста? - -## Результаты -В результате проделанной оптимизации наконец удалось обработать файл с данными. -Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет. - -*Какими ещё результами можете поделиться* - -## Защита от регрессии производительности -Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* - - - -Добавил прогресс бары - -### находка №1 Многократная итерация объекта sessions для создания users_objects +### находка №1 Многократная итерация объекта sessions для создания users_objects - callstack из ruby-prof -- одной итерацией собрать необходимые данные в объекте sessions_by_user -- время выполнения приложения на 15т строк сократилась 7.5 секунд до 0.9 секунд -- исправленная проблема перестала быть главной точкой роста - +- Одной итерацией собрать необходимые данные в объекте sessions_by_user +- Время выполнения приложения на 15т строк сократилась 7.5 секунд до 0.9 секунд +- Исправленная проблема перестала быть главной точкой роста ### находка №2 Неэффективный алгоритм с многократными проверками для сбора unique_browsers. - callstack из ruby-prof - Было принято решение заменить неэффективный алгоритм с многократными проверками на более оптимизированное решение, использующее встроенные методы Ruby -- время выполнения приложения на 15т строк сократилась 0.9 секунд до 0.6 секунд -- исправленная проблема перестала быть главной точкой роста +- Время выполнения приложения на 15т строк сократилась 0.9 секунд до 0.6 секунд +- Исправленная проблема перестала быть главной точкой роста ### Находка №3: Неэффективное добавление элементов в массивы с использованием оператора конкатенации - callstack из ruby-prof - Было принято решение заменить неэффективное добавление элементов в массивы с помощью оператора + на использование метода << (shovel operator). Дополнительно была применена конструкция case для улучшения читаемости и производительности. Время выполнения: точные измерения не предоставлены, но ожидается значительное улучшение производительности, особенно для больших наборов данных. Операция << имеет сложность O(1), тогда как + создает новый массив при каждой итерации, что имеет сложность O(n). -- время выполнения приложения на 15т строк сократилась 0.6 секунд до 0.5 секунд -- исправленная проблема перестала быть главной точкой роста +- Время выполнения приложения на 15т строк сократилась 0.6 секунд до 0.5 секунд +- Исправленная проблема перестала быть главной точкой роста + +### Находка №4: Избыточный парсинг дат и преобразование в формат iso8601 +- callstack из ruby-prof +- Так как мы уже имеем данные в нужном формате, то было принято решение не тратить время на преобразование даты в формат iso8601 +- Исправленная проблема перестала быть главной точкой роста + +### Находка №5: Избыточное использование хешей для представления сессий +- callstack из ruby-prof +- Анализ профилировщика показал, что создание и обработка хешей в методе parse_session занимают значительное время, особенно при большом количестве сессий. Это связано с накладными расходами на создание объектов хешей и доступ к их ключам. +- Было принято решение заменить использование хешей на Struct для представления сессий. Struct предоставляет более легковесную и быструю структуру для хранения данных с фиксированными ключами, что уменьшает время создания объектов и ускоряет доступ к их атрибутам. +- Исправленная проблема перестала быть главной точкой роста. Данные начали обрабатываться быстрее и укладываться в бюджет времени. + +## Результаты +В результате проделанной оптимизации наконец удалось обработать файл с данными. +Удалось улучшить метрику системы с более чем 15 минут обработки файла до 30 секунд и уложиться в заданный бюджет. + +## Защита от регрессии производительности +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы я написал performance_test.rb. diff --git a/performance_test.rb b/performance_test.rb new file mode 100644 index 00000000..f43a6cbe --- /dev/null +++ b/performance_test.rb @@ -0,0 +1,14 @@ +require 'rspec-benchmark' +require_relative 'task-1' + +RSpec.configure do |config| + config.include RSpec::Benchmark::Matchers +end + +describe 'Task-1 Performance' do + describe '#work method' do + it 'completes the operation within 30 seconds' do + expect { work(file_name: "data_large.txt", progress_bar: false) }.to perform_under(30).sec + end + end +end diff --git a/run_profiling.rb b/run_profiling.rb new file mode 100644 index 00000000..02fe5984 --- /dev/null +++ b/run_profiling.rb @@ -0,0 +1,11 @@ +require_relative 'task-1.rb' +require 'ruby-prof' + +RubyProf.measure_mode = RubyProf::WALL_TIME + +result = RubyProf.profile { work(file_name: '100000.txt', disable_gc: true, progress_bar: false) } + +RubyProf::FlatPrinter.new(result).print(File.open("ruby_prof_reports/flat.txt", "w+")) +RubyProf::GraphHtmlPrinter.new(result).print(File.open("ruby_prof_reports/graph.html", "w+")) +RubyProf::CallStackPrinter.new(result).print(File.open('ruby_prof_reports/callstack.html', 'w+')) +RubyProf::CallTreePrinter.new(result).print(:path => "ruby_prof_reports", :profile => 'callgrind') diff --git a/task-1.rb b/task-1.rb index f230f6ca..caf675ad 100644 --- a/task-1.rb +++ b/task-1.rb @@ -1,9 +1,4 @@ -# Deoptimized version of homework task - require 'json' -require 'pry' -require 'date' -require 'minitest/autorun' require 'ruby-progressbar' class User @@ -25,15 +20,11 @@ def parse_user(user) } end +Session = Struct.new(:user_id, :session_id, :browser, :time, :date) + 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], - } + _, user_id, session_id, browser, time, date = session.split(',', 6) + Session.new(user_id, session_id, browser, time, date) end def collect_stats_from_users(report, users_objects, &block) @@ -149,7 +140,7 @@ def work(file_name: "data.txt", disable_gc: false, progress_bar: true) # Даты сессий через запятую в обратном порядке в формате 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 } } + { 'dates' => user.sessions.map{|s| s['date']}.sort.reverse } end File.write('result.json', "#{report.to_json}\n") @@ -158,35 +149,3 @@ def work(file_name: "data.txt", disable_gc: false, progress_bar: true) def increment_progressbar(progressbar) progressbar&.increment end - -class TestMe < Minitest::Test - def setup - File.write('result.json', '') - File.write('data.txt', -'user,0,Leida,Cira,0 -session,0,0,Safari 29,87,2016-10-23 -session,0,1,Firefox 12,118,2017-02-27 -session,0,2,Internet Explorer 28,31,2017-03-28 -session,0,3,Internet Explorer 28,109,2016-09-15 -session,0,4,Safari 39,104,2017-09-27 -session,0,5,Internet Explorer 35,6,2016-09-01 -user,1,Palmer,Katrina,65 -session,1,0,Safari 17,12,2016-10-21 -session,1,1,Firefox 32,3,2016-12-20 -session,1,2,Chrome 6,59,2016-11-11 -session,1,3,Internet Explorer 10,28,2017-04-29 -session,1,4,Chrome 13,116,2016-12-28 -user,2,Gregory,Santos,86 -session,2,0,Chrome 35,6,2018-09-21 -session,2,1,Safari 49,85,2017-05-22 -session,2,2,Firefox 47,17,2018-02-02 -session,2,3,Chrome 20,84,2016-11-25 -') - end - - def test_result - work - expected_result = '{"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"]}}}' + "\n" - assert_equal expected_result, File.read('result.json') - end -end diff --git a/test.rb b/test.rb new file mode 100644 index 00000000..59aa4a85 --- /dev/null +++ b/test.rb @@ -0,0 +1,35 @@ +require 'minitest/autorun' +require_relative 'task-1.rb' + +class TestMe < Minitest::Test + def setup + File.write('result.json', '') + File.write('data.txt', + 'user,0,Leida,Cira,0 +session,0,0,Safari 29,87,2016-10-23 +session,0,1,Firefox 12,118,2017-02-27 +session,0,2,Internet Explorer 28,31,2017-03-28 +session,0,3,Internet Explorer 28,109,2016-09-15 +session,0,4,Safari 39,104,2017-09-27 +session,0,5,Internet Explorer 35,6,2016-09-01 +user,1,Palmer,Katrina,65 +session,1,0,Safari 17,12,2016-10-21 +session,1,1,Firefox 32,3,2016-12-20 +session,1,2,Chrome 6,59,2016-11-11 +session,1,3,Internet Explorer 10,28,2017-04-29 +session,1,4,Chrome 13,116,2016-12-28 +user,2,Gregory,Santos,86 +session,2,0,Chrome 35,6,2018-09-21 +session,2,1,Safari 49,85,2017-05-22 +session,2,2,Firefox 47,17,2018-02-02 +session,2,3,Chrome 20,84,2016-11-25 +') + end + + def test_result + work(progress_bar: false) + expected_result = '{"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"]}}}' + "\n" + assert_equal expected_result, File.read('result.json') + end +end +