diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..c0f55f0a Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..30fd6aa1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +data* +!data.txt +result.json +.*/ +stackprof_reports/ +ruby_prof_reports/ +docker-valgrind/ \ No newline at end of file diff --git a/case-study-template.md b/case-study-template.md deleted file mode 100644 index c3279664..00000000 --- a/case-study-template.md +++ /dev/null @@ -1,55 +0,0 @@ -# Case-study оптимизации - -## Актуальная проблема -В нашем проекте возникла серьёзная проблема. - -Необходимо было обработать файл с данными, чуть больше ста мегабайт. - -У нас уже была программа на `ruby`, которая умела делать нужную обработку. - -Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время. - -Я решил исправить эту проблему, оптимизировав эту программу. - -## Формирование метрики -Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: *тут ваша метрика* - -## Гарантия корректности работы оптимизированной программы -Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. - -## Feedback-Loop -Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* - -Вот как я построил `feedback_loop`: *как вы построили feedback_loop* - -## Вникаем в детали системы, чтобы найти главные точки роста -Для того, чтобы найти "точки роста" для оптимизации я воспользовался *инструментами, которыми вы воспользовались* - -Вот какие проблемы удалось найти и решить - -### Ваша находка №1 -- какой отчёт показал главную точку роста -- как вы решили её оптимизировать -- как изменилась метрика -- как изменился отчёт профилировщика - -### Ваша находка №2 -- какой отчёт показал главную точку роста -- как вы решили её оптимизировать -- как изменилась метрика -- как изменился отчёт профилировщика - -### Ваша находка №X -- какой отчёт показал главную точку роста -- как вы решили её оптимизировать -- как изменилась метрика -- как изменился отчёт профилировщика - -## Результаты -В результате проделанной оптимизации наконец удалось обработать файл с данными. -Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет. - -*Какими ещё результами можете поделиться* - -## Защита от регрессии производительности -Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* diff --git a/case-study.md b/case-study.md new file mode 100644 index 00000000..97cbc5b6 --- /dev/null +++ b/case-study.md @@ -0,0 +1,70 @@ +# Case-study оптимизации + +## Актуальная проблема +В нашем проекте возникла серьёзная проблема. + +Необходимо было обработать файл с данными, чуть больше ста мегабайт. + +У нас уже была программа на `ruby`, которая умела делать нужную обработку. + +Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время. + +Я решила исправить эту проблему, оптимизировав эту программу. + +## Формирование метрики +Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумала использовать такую метрику: количество потребляемой программой памяти, бюджет на метрику - 70Мб. + +## Гарантия корректности работы оптимизированной программы +Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. + +## Feedback-Loop +Для того, чтобы иметь возможность быстро проверять гипотезы я выстроила эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений менее чем за минуту. + +Вот как я построил `feedback_loop`: +- Запускаю профилировщик при выполнении программы +- Анализирую и выделяю главную точку роста +- Вношу изменения в программу +- Прогоняю тест, чтобы убедиться, что все по прежнему работает как задумано +- Вычисляю новое значение метрики, смотрю, изменилось ли количество потребляемой памяти в меньшую сторону +- Если да, запускаю еще раз профилировщик, проверяю, что главная точка роста изменилась +- Сохраняю изменения и делаю коммит + +## Вникаем в детали системы, чтобы найти главные точки роста +Для того, чтобы найти "точки роста" для оптимизации я воспользовалась профилировщиками `ruby-prof`, `stackprof`, гемом `memory-profiler`, инструментом `valgrind massif visualizer`. + +Так как программа изначально считывала в память и записывала в переменные большой объем данных, первым шагом я переписала ее в потоковом стиле. Я начала считывать входной файл по строкам, а писать JSON при помощи гема `Oj`. Для подсчета уникальных юзеров и сессий я ввела две отдельные переменные, которые инкрементировала по ходу обработки входного файла, а для вывода уникальных браузеров добавила структуру `Set`. Сессии обрабатываемого в данный момент юзера я добавляла в переменную `current_sessions`, которую передавала далее в метод `write_stats_for` для вычисления и тут же записи статистики по юзеру. После того, как я переписала программу в потоковом стиле, я начала искать точки роста при помощи профилировщиков. + +Вот какие проблемы удалось найти и решить + +### Date.parse() +- `memory-profiler` показывает строку + ```ruby + writer.push_value(sessions.map{|s| s['date']}.map {|d| Date.parse(d)}.sort.reverse.map { |d| d.iso8601 }) + ``` +- Выделила `sessions.map{|s| s['date']}` в отдельную переменную, к которой применяла bang-методы, переписала парсинг даты: + ```ruby + dates = sessions.map{|s| s['date']} + writer.push_key('dates') + writer.push_value(dates + .sort! + .reverse! + .map! do |d| + ary = d.split('-') + Date.new(ary[0].to_i, ary[1].to_i, ary[2].to_i).iso8601 + end) + ``` +- Потребление памяти снизилось с 22Мб до 20Мб +- `memory-profiler` перестал показывать эту строку, как аллоцирующую максимальный объем памяти + +### Array.map() +- Отчеты `ruby-prof` в режимах `callgrind` и `graph` +- Вызовы метода `map` внутри `write_stats_for` занимали много памяти. Вынесла общие вызовы для `sessions.map` в переменные, использовала bang-методы +- Метрика снизилась до 20Мб +- В `callgrind` отчете строчка с `map` методом занимала примерно 8% памяти, стала занимать 2% + +## Результаты +В результате проделанной оптимизации наконец удалось обработать файл с данными. +Удалось улучшить метрику системы с и уложиться в заданный бюджет. + +## Защита от регрессии производительности +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы я добавила `RSpec` тест, проверяющий количество аллоцированных байт по разным типам данных при работе программы на тестовом файле (тест `spec/task-2_spec.rb`) diff --git a/data_large.txt.gz b/data_large.txt.gz deleted file mode 100644 index 91c7e45e..00000000 Binary files a/data_large.txt.gz and /dev/null differ diff --git a/memory-profiler-work.rb b/memory-profiler-work.rb new file mode 100644 index 00000000..1f0e9a38 --- /dev/null +++ b/memory-profiler-work.rb @@ -0,0 +1,7 @@ +require_relative 'task-2.rb' +require 'memory_profiler' + +report = MemoryProfiler.report do + work('data.txt', disable_gc: false) +end +report.pretty_print(scale_bytes: true) \ No newline at end of file diff --git a/ruby-prof-allocations-work.rb b/ruby-prof-allocations-work.rb new file mode 100644 index 00000000..1c6d0047 --- /dev/null +++ b/ruby-prof-allocations-work.rb @@ -0,0 +1,24 @@ +require 'ruby-prof' +require_relative 'task-2.rb' + +RubyProf.measure_mode = RubyProf::ALLOCATIONS + +profile = RubyProf::Profile.new +result = profile.profile do + work('data_small.txt', disable_gc: true) +end + +printer = RubyProf::FlatPrinter.new(result) +printer.print(File.open('ruby_prof_reports/flat.txt', 'w+')) + +printer = RubyProf::DotPrinter.new(result) +printer.print(File.open('ruby_prof_reports/graphviz.dot', 'w+')) + +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+')) + +printer = RubyProf::CallTreePrinter.new(result) +printer.print(path: 'ruby_prof_reports', profile: 'profile') \ No newline at end of file diff --git a/ruby-prof-memory-work.rb b/ruby-prof-memory-work.rb new file mode 100644 index 00000000..c08a0c37 --- /dev/null +++ b/ruby-prof-memory-work.rb @@ -0,0 +1,24 @@ +require 'ruby-prof' +require_relative 'task-2.rb' + +RubyProf.measure_mode = RubyProf::MEMORY + +profile = RubyProf::Profile.new +result = profile.profile do + work('data_small.txt', disable_gc: true) +end + +printer = RubyProf::FlatPrinter.new(result) +printer.print(File.open('ruby_prof_reports/flat_memory.txt', 'w+')) + +printer = RubyProf::DotPrinter.new(result) +printer.print(File.open('ruby_prof_reports/graphviz_memory.dot', 'w+')) + +printer = RubyProf::GraphHtmlPrinter.new(result) +printer.print(File.open('ruby_prof_reports/graph_memory.html', 'w+')) + +printer = RubyProf::CallStackPrinter.new(result) +printer.print(File.open('ruby_prof_reports/callstack_memory.html', 'w+')) + +printer = RubyProf::CallTreePrinter.new(result) +printer.print(path: 'ruby_prof_reports', profile: 'profile') \ No newline at end of file diff --git a/spec/task-2_spec.rb b/spec/task-2_spec.rb new file mode 100644 index 00000000..0b21cc14 --- /dev/null +++ b/spec/task-2_spec.rb @@ -0,0 +1,40 @@ +require 'rspec-benchmark' +require_relative '../task-2' + +RSpec.configure do |config| + config.include RSpec::Benchmark::Matchers +end + +describe 'Perfomance' do + before do + 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 + + describe 'task 2' do + it 'permorms allocations' do + expect { + work('data.txt') + }.to perform_allocation({String => 20000, Array => 8000, Date => 1400, Hash => 6000}).bytes + end + end +end \ No newline at end of file diff --git a/stackprof-work.rb b/stackprof-work.rb new file mode 100644 index 00000000..4dbce334 --- /dev/null +++ b/stackprof-work.rb @@ -0,0 +1,6 @@ +require 'stackprof' +require_relative 'task-2' + +StackProf.run(mode: :object, out: 'stackprof_reports/stackprof.dump', raw: true) do + work('data_small.txt', disable_gc: false) +end \ No newline at end of file diff --git a/task-2.rb b/task-2.rb index 34e09a3c..230a6659 100644 --- a/task-2.rb +++ b/task-2.rb @@ -1,19 +1,113 @@ -# Deoptimized version of homework task - require 'json' require 'pry' require 'date' -require 'minitest/autorun' +require 'oj' +require 'set' class User - attr_reader :attributes, :sessions + attr_reader :attributes - def initialize(attributes:, sessions:) + def initialize(attributes:) @attributes = attributes - @sessions = sessions end end +# Отчёт в json +# - Сколько всего юзеров + +# - Сколько всего уникальных браузеров + +# - Сколько всего сессий + +# - Перечислить уникальные браузеры в алфавитном порядке через запятую и капсом + +# +# - По каждому пользователю +# - сколько всего сессий + +# - сколько всего времени + +# - самая длинная сессия + +# - браузеры через запятую + +# - Хоть раз использовал IE? + +# - Всегда использовал только Хром? + +# - даты сессий в порядке убывания через запятую + +def parse_file(file_in, file_out) + writer = Oj::StreamWriter.new(file_out) + writer.push_object + + session_count = 0 + user_count = 0 + browsers = Set.new + + current_sessions = [] + user = nil + session = nil + + writer.push_key('usersStats') + writer.push_object + file_in.each_line do |line| + if line.start_with? 'user' + write_stats_for(user, current_sessions, writer) unless current_sessions.empty? + user = User.new(attributes: parse_user(line)) + user_count += 1 + current_sessions = [] + elsif line.start_with? 'session' + session = parse_session(line) + current_sessions << session + session_count += 1 + browsers << session['browser'] + end + end + write_stats_for(user, current_sessions, writer) unless current_sessions.empty? + writer.pop + + writer.push_key('totalUsers') + writer.push_value(user_count) + + writer.push_key('uniqueBrowsersCount') + writer.push_value(browsers.size) + + writer.push_key('totalSessions') + writer.push_value(session_count) + + writer.push_key('allBrowsers') + writer.push_value(browsers.to_a.map { |b| b.upcase }.sort.join(',')) + + writer.pop +end + +def write_stats_for(user, sessions, writer) + user_key = "#{user.attributes['first_name']}" + ' ' + "#{user.attributes['last_name']}" + writer.push_key(user_key) + writer.push_object + + writer.push_key('sessionsCount') + writer.push_value(sessions.count) + + times = sessions.map {|s| s['time']}.map! {|t| t.to_i} + writer.push_key('totalTime') + writer.push_value(times.sum.to_s + ' min.') + + writer.push_key('longestSession') + writer.push_value(times.max.to_s + ' min.') + + browsers = sessions.map {|s| s['browser']}.map! {|b| b.upcase} + writer.push_key('browsers') + writer.push_value(browsers.sort.join(', ')) + + writer.push_key('usedIE') + writer.push_value(browsers.any? { |b| b =~ /INTERNET EXPLORER/ }) + + writer.push_key('alwaysUsedChrome') + writer.push_value(browsers.all? { |b| b =~ /CHROME/ }) + + dates = sessions.map{|s| s['date']} + writer.push_key('dates') + writer.push_value(dates + .sort! + .reverse! + .map! do |d| + ary = d.split('-') + Date.new(ary[0].to_i, ary[1].to_i, ary[2].to_i).iso8601 + end) + writer.pop +end + def parse_user(user) fields = user.split(',') parsed_result = { @@ -35,143 +129,18 @@ def parse_session(session) } end -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 - -def work - file_lines = File.read('data.txt').split("\n") - - users = [] - sessions = [] - - 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 - - # Отчёт в json - # - Сколько всего юзеров + - # - Сколько всего уникальных браузеров + - # - Сколько всего сессий + - # - Перечислить уникальные браузеры в алфавитном порядке через запятую и капсом + - # - # - По каждому пользователю - # - сколько всего сессий + - # - сколько всего времени + - # - самая длинная сессия + - # - браузеры через запятую + - # - Хоть раз использовал IE? + - # - Всегда использовал только Хром? + - # - даты сессий в порядке убывания через запятую + - - report = {} - - report[:totalUsers] = users.count - - # Подсчёт количества уникальных браузеров - uniqueBrowsers = [] - sessions.each do |session| - browser = session['browser'] - uniqueBrowsers += [browser] if uniqueBrowsers.all? { |b| b != browser } - end - - report['uniqueBrowsersCount'] = uniqueBrowsers.count - - report['totalSessions'] = sessions.count +def work(filename = '', disable_gc: true) + puts "Start work" + GC.disable if disable_gc - report['allBrowsers'] = - sessions - .map { |s| s['browser'] } - .map { |b| b.upcase } - .sort - .uniq - .join(',') + file_in = File.open(filename) + file_out = File.open('result.json', 'w') - # Статистика по пользователям - users_objects = [] + parse_file(file_in, file_out) - 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 - - report['usersStats'] = {} - - # Собираем количество сессий по пользователям - collect_stats_from_users(report, users_objects) do |user| - { 'sessionsCount' => user.sessions.count } - 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 + file_in.close + file_out.close - # Выбираем самую длинную сессию пользователя - 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 - - # Браузеры пользователя через запятую - collect_stats_from_users(report, users_objects) do |user| - { 'browsers' => user.sessions.map {|s| s['browser']}.map {|b| b.upcase}.sort.join(', ') } - end - - # Хоть раз использовал IE? - collect_stats_from_users(report, users_objects) do |user| - { 'usedIE' => user.sessions.map{|s| s['browser']}.any? { |b| b.upcase =~ /INTERNET EXPLORER/ } } - end - - # Всегда использовал только Chrome? - collect_stats_from_users(report, users_objects) do |user| - { 'alwaysUsedChrome' => user.sessions.map{|s| s['browser']}.all? { |b| b.upcase =~ /CHROME/ } } - 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 } } - end - - File.write('result.json', "#{report.to_json}\n") + puts "Finish work" puts "MEMORY USAGE: %d MB" % (`ps -o rss= -p #{Process.pid}`.to_i / 1024) 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 = 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 -end diff --git a/test/task2_test.rb b/test/task2_test.rb new file mode 100644 index 00000000..715765ce --- /dev/null +++ b/test/task2_test.rb @@ -0,0 +1,34 @@ +require 'minitest/autorun' +require_relative '../task-2' + +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('data.txt') + 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 +end diff --git a/work.rb b/work.rb new file mode 100644 index 00000000..8a1b0e05 --- /dev/null +++ b/work.rb @@ -0,0 +1,3 @@ +require_relative 'task-2' + +work('data_large.txt', disable_gc: false) \ No newline at end of file