diff --git a/case-study.md b/case-study.md new file mode 100644 index 00000000..81f7003f --- /dev/null +++ b/case-study.md @@ -0,0 +1,64 @@ +## Формирование метрики +Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я решил использовать такую метрику: Rspec тест проверяющий затрачиваемую память + +## Гарантия корректности работы оптимизированной программы +Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. + +## Feedback-Loop +Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* + +Вот как я построил `feedback_loop`: По примеру работы с первой задачей сделал выборки из data_large на + +## Вникаем в детали системы, чтобы найти главные точки роста +Для того, чтобы найти "точки роста" для оптимизации я воспользовался *инструментами, которыми вы воспользовались* + +Вот какие проблемы удалось найти и решить + +### Ваша находка №1 +- какой отчёт показал главную точку роста +- как вы решили её оптимизировать +- как изменилась метрика +- как изменился отчёт профилировщика + +### Ваша находка №2 +- какой отчёт показал главную точку роста +- как вы решили её оптимизировать +- как изменилась метрика +- как изменился отчёт профилировщика + +### Ваша находка №X +- какой отчёт показал главную точку роста +- как вы решили её оптимизировать +- как изменилась метрика +- как изменился отчёт профилировщика + +## Результаты +В результате проделанной оптимизации наконец удалось обработать файл с данными. +Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет. + +*Какими ещё результами можете поделиться* + +## Защита от регрессии производительности +Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* + +### Ваша находка №1 +При первом прогоне профайлера выявлено значительное потребление памяти на сложении массивов(более 70 процентов потребления). + sessions = sessions + [parse_session(line)] if cols[0] == 'session' +В комментариях к выполнению задачи было указание на переписывание программы на потоковый стиль. +Начнем сразу +т.к в файлах данных строгая последовательность пользователь-сессия-сессия, то можно заинициализировать класс и добавлять в него данные по мере наполнения, а после полного сбора привести их к требуемому формату +### Ваша находка №2 +Таким образом мы обработаем второй тяжелый поинт из отчета не пробегая по массиву пользователей раз за разом +user_sessions = sessions.select { |session| session['user_id'] == user['id'] } +### Ваша находка №3 +При очередном прогоне теста было замечено высокое потребление на моменте перебора строк файла через each. Написав небольшой бенчмарк для сравнения скорости работы методов чтения из файла был получен метод foreach который в 8 раз быстрее обрабатывает файл построчно. +### Ваша находка №4 +Сначала я пытался добавлять пользователей в массив, дообогащать его рассчетами и записывать в файл преобразованное в json, +но при прогоне теста было большое потребление на обработку файловых операций. +Немного костыльными методами было решено разбить структуру предполагаемого файла на отдельные элементы, включая технические "{}, ", ," и дописывать в файл по мере необходимости. + +## Результаты +В результате проделанной оптимизации удалось обработать файл с данными 1кк c целевыми показателями по использованной памяти. Причем показатели потребления не меняются при увеличении обьема данных. + +## Примечания +При первой итерации пробовал пойти через перенос структуры данных в отдельные файлы. И гипотеза успешно отработала на файле до 1кк записей, но при увеличении объема затраты на запись и чтение свели весь прогресс на нет. \ No newline at end of file diff --git a/profiler.rb b/profiler.rb new file mode 100644 index 00000000..4d030c63 --- /dev/null +++ b/profiler.rb @@ -0,0 +1,8 @@ +require_relative 'task-2.rb' +require 'benchmark' +require 'memory_profiler' + +report = MemoryProfiler.report do + work(file_name: ARGV[0]) +end +report.pretty_print(scale_bytes: true) \ No newline at end of file diff --git a/profiler1.rb b/profiler1.rb new file mode 100644 index 00000000..61e92c8e --- /dev/null +++ b/profiler1.rb @@ -0,0 +1,22 @@ +require_relative 'task-2.rb' +require 'benchmark' +require 'ruby-prof' + + +RubyProf.measure_mode = RubyProf::ALLOCATIONS + +result = RubyProf.profile do + work(file_name: ARGV[0]) +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+')) \ No newline at end of file diff --git a/ram_spec.rb b/ram_spec.rb new file mode 100644 index 00000000..5c78207f --- /dev/null +++ b/ram_spec.rb @@ -0,0 +1,12 @@ +require 'rspec' +require 'rspec-benchmark' +require_relative 'task-2' + +RSpec.describe 'work' do + include RSpec::Benchmark::Matchers + + it 'requires < 60 Mb' do + work(file_name: 'data_large.txt') + expect((`ps -o rss= -p #{Process.pid}`.to_i / 1024)).to be < 60 + end +end diff --git a/ruby_prof_reports/callstack.html b/ruby_prof_reports/callstack.html new file mode 100644 index 00000000..2c9c542f --- /dev/null +++ b/ruby_prof_reports/callstack.html @@ -0,0 +1,706 @@ + + + + + ruby-prof call tree + + + + + + + +
+
+ Call tree for application profiler1.rb data_large.txt
Generated on 2024-06-25 11:49:02 +0300 + with options {}
+
+
+ Threshold: + + + + + +
+ + + + +
+ Thread: 260, Fiber: 240 ( NaN% ~ 0.0) + +
+ +
+
+ + diff --git a/ruby_prof_reports/flat.txt b/ruby_prof_reports/flat.txt new file mode 100644 index 00000000..d64da3a0 --- /dev/null +++ b/ruby_prof_reports/flat.txt @@ -0,0 +1,84 @@ +Measure Mode: allocations +Thread ID: 260 +Fiber ID: 240 +Total: 0.000000 +Sort by: self_time + + %self total self wait child calls name location + NaN 0.000 0.000 0.000 0.000 1 String#% + NaN 0.000 0.000 0.000 0.000 1 Integer#/ + NaN 0.000 0.000 0.000 0.000 1 Kernel#` + NaN 0.000 0.000 0.000 0.000 1 #pid + NaN 0.000 0.000 0.000 0.000 3 Integer#inspect + NaN 0.000 0.000 0.000 0.000 5 String#inspect + NaN 0.000 0.000 0.000 0.000 1 Hash#to_s + NaN 0.000 0.000 0.000 0.000 3 IO#puts + NaN 0.000 0.000 0.000 0.000 3 Kernel#puts + NaN 0.000 0.000 0.000 0.000 1 IO#close + NaN 0.000 0.000 0.000 0.000 1 String#to_s + NaN 0.000 0.000 0.000 0.000 1 String#slice + NaN 0.000 0.000 0.000 0.000 1 Array#size + NaN 0.000 0.000 0.000 0.000 500000 User#sessions= + NaN 0.000 0.000 0.000 0.000 500000 Integer#- + NaN 0.000 0.000 0.000 0.000 500000 String#length + NaN 0.000 0.000 0.000 0.000 1000000 String#== + NaN 0.000 0.000 0.000 0.000 1500000 String#[] + NaN 0.000 0.000 0.000 0.000 500001 JSON::Ext::Generator::State#initialize + NaN 0.000 0.000 0.000 0.000 500001 JSON::Ext::Generator::GeneratorMethods::Hash#to_json + NaN 0.000 0.000 0.000 0.000 500000 Integer#> + NaN 0.000 0.000 0.000 0.000 500000 Object#save_user /mnt/c/Users/Home/study/mytask-2/task-2.rb:74 + NaN 0.000 0.000 0.000 0.000 500000 Array#reverse! + NaN 0.000 0.000 0.000 0.000 500001 Array#sort! + NaN 0.000 0.000 0.000 0.000 500001 Array#join + NaN 0.000 0.000 0.000 0.000 500000 Array#sort + NaN 0.000 0.000 0.000 0.000 500000 Array#all? + NaN 0.000 0.000 0.000 0.000 2075737 String#=~ + NaN 0.000 0.000 0.000 0.000 500000 Array#any? + NaN 0.000 0.000 0.000 0.000 2750940 BasicObject#! + NaN 0.000 0.000 0.000 0.000 2750940 Array#include? + NaN 0.000 0.000 0.000 0.000 2750940 String#upcase! + NaN 0.000 0.000 0.000 0.000 2750940 Integer#< + NaN 0.000 0.000 0.000 0.000 2750941 String#to_i + NaN 0.000 0.000 0.000 0.000 500000 Array#each + NaN 0.000 0.000 0.000 0.000 500000 Enumerable#inject + NaN 0.000 0.000 0.000 0.000 8253020 Array#<< + NaN 0.000 0.000 0.000 0.000 2750940 Object#parse_session /mnt/c/Users/Home/study/mytask-2/task-2.rb:25 + NaN 0.000 0.000 0.000 0.000 3250940 User#sessions + NaN 0.000 0.000 0.000 0.000 8752820 Hash#[]= + NaN 0.000 0.000 0.000 0.000 8752820 Integer#+ + NaN 0.000 0.000 0.000 0.000 8752820 Hash#[] + NaN 0.000 0.000 0.000 0.000 1000000 User#attributes= + NaN 0.000 0.000 0.000 0.000 1000000 String#<< + NaN 0.000 0.000 0.000 0.000 500000 Object#parse_user /mnt/c/Users/Home/study/mytask-2/task-2.rb:17 + NaN 0.000 0.000 0.000 0.000 1000001 User#attributes + NaN 0.000 0.000 0.000 0.000 500001 Object#collect_stats_from_users /mnt/c/Users/Home/study/mytask-2/task-2.rb:35 + NaN 0.000 0.000 0.000 0.000 3250940 String#split + NaN 0.000 0.000 0.000 0.000 3250940 String#chop + NaN 0.000 0.000 0.000 0.000 1 #foreach + NaN 0.000 0.000 0.000 0.000 1 User#initialize /mnt/c/Users/Home/study/mytask-2/task-2.rb:11 + NaN 0.000 0.000 0.000 0.000 1 Class#new + NaN 0.000 0.000 0.000 0.000 1000005 IO#write + NaN 0.000 0.000 0.000 0.000 1 File#initialize + NaN 0.000 0.000 0.000 0.000 1 #open + NaN 0.000 0.000 0.000 0.000 1 Object#work /mnt/c/Users/Home/study/mytask-2/task-2.rb:85 + NaN 0.000 0.000 0.000 0.000 19005641 Array#[] + NaN 0.000 0.000 0.000 0.000 1 [global]# profiler1.rb:9 + +* recursively called methods + +Columns are: + + %self - The percentage of time spent in this method, derived from self_time/total_time. + total - The time spent in this method and its children. + self - The time spent in this method. + wait - The amount of time this method waited for other threads. + child - The time spent in this method's children. + calls - The number of times this method was called. + name - The name of the method. + location - The location of the method. + +The interpretation of method names is: + + * MyObject#test - An instance method "test" of the class "MyObject" + * #test - The <> characters indicate a method on a singleton class. + diff --git a/ruby_prof_reports/graph.html b/ruby_prof_reports/graph.html new file mode 100644 index 00000000..aa406223 --- /dev/null +++ b/ruby_prof_reports/graph.html @@ -0,0 +1,4049 @@ + + + + + + +
+
+
+
Profile Report
+

Allocations

+
+
+
Tuesday, June 25 at 11:49:02 AM (MSK)
+ +
+
+
+
+ + + + + + + + + + + + + +
Thread IDFiber IDTotal
2602400.0
+ + + +

Thread 260, Fiber: 240

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
%Total%SelfTotalSelfWaitChildCallsNameLine
NaN%NaN%0.000.000.000.001 + + [global]# + + 9
  0.000.000.000.001/1Object#work9 +
  0.000.000.000.001/19005641Array#[]9 +
  0.000.000.000.001/1[global]#9
NaN%NaN%0.000.000.000.001 + + Object#work + + 85
  0.000.000.000.001/1String#%139 +
  0.000.000.000.001/1Integer#/139 +
  0.000.000.000.001/2750941String#to_i139 +
  0.000.000.000.001/1Kernel#`139 +
  0.000.000.000.001/1<Module::Process>#pid139 +
  0.000.000.000.003/3Kernel#puts121 +
  0.000.000.000.001/1IO#close119 +
  0.000.000.000.001/1String#to_s118 +
  0.000.000.000.001/1String#slice118 +
  0.000.000.000.001/500001JSON::Ext::Generator::GeneratorMethods::Hash#to_json118 +
  0.000.000.000.001/500001Array#join115 +
  0.000.000.000.001/500001Array#sort!115 +
  0.000.000.000.001/1Array#size114 +
  0.000.000.000.001/500001Object#collect_stats_from_users112 +
  0.000.000.000.001/1<Class::IO>#foreach98 +
  0.000.000.000.001/1Class#new96 +
  0.000.000.000.003/1000005IO#write94 +
  0.000.000.000.001/1<Class::IO>#open93 +
  0.000.000.000.001/19005641[global]#9
  0.000.000.000.003250940/19005641<Class::IO>#foreach
  0.000.000.000.002000000/19005641Object#parse_user19
  0.000.000.000.0013754700/19005641Object#parse_session27
NaN%NaN%0.000.000.000.0019005641 + + Array#[] + +
  0.000.000.000.001/1Object#work118
NaN%NaN%0.000.000.000.001 + + String#to_s + +
  0.000.000.000.002750940/2750941Array#each
  0.000.000.000.001/2750941Object#work139
NaN%NaN%0.000.000.000.002750941 + + String#to_i + +
  0.000.000.000.001/1Object#work118
NaN%NaN%0.000.000.000.001 + + String#slice + +
  0.000.000.000.001/1Object#work139
NaN%NaN%0.000.000.000.001 + + String#% + +
  0.000.000.000.00500000/500001<Class::IO>#foreach
  0.000.000.000.001/500001Object#work112
NaN%NaN%0.000.000.000.00500001 + + Object#collect_stats_from_users + + 35
  0.000.000.000.00500000/500000Object#save_user71 +
  0.000.000.000.00500000/500000Array#reverse!69 +
  0.000.000.000.00500000/500001Array#sort!69 +
  0.000.000.000.00500000/500001Array#join68 +
  0.000.000.000.00500000/500000Array#sort68 +
  0.000.000.000.00500000/500000Array#all?64 +
  0.000.000.000.00500000/500000Array#any?62 +
  0.000.000.000.00500000/500000Enumerable#inject38 +
  0.000.000.000.00500000/3250940User#sessions38 +
  0.000.000.000.00500001/1000001User#attributes36 +
  0.000.000.000.003/3Object#work121
NaN%NaN%0.000.000.000.003 + + Kernel#puts + +
  0.000.000.000.003/3IO#puts +
  0.000.000.000.001/1Object#work139
NaN%NaN%0.000.000.000.001 + + Kernel#` + +
  0.000.000.000.00500000/500001Object#save_user78
  0.000.000.000.001/500001Object#work118
NaN%NaN%0.000.000.000.00500001 + + JSON::Ext::Generator::GeneratorMethods::Hash#to_json + +
  0.000.000.000.00500001/500001JSON::Ext::Generator::State#initialize +
  0.000.000.000.001/1Object#work139
NaN%NaN%0.000.000.000.001 + + Integer#/ + +
  0.000.000.000.003/1000005Object#work94
  0.000.000.000.00999999/1000005Object#save_user80
  0.000.000.000.003/1000005IO#puts
NaN%NaN%0.000.000.000.001000005 + + IO#write + +
  0.000.000.000.001/1Object#work119
NaN%NaN%0.000.000.000.001 + + IO#close + +
  0.000.000.000.001/1Object#work96
NaN%NaN%0.000.000.000.001 + + Class#new + +
  0.000.000.000.001/1User#initialize +
  0.000.000.000.00500000/500001Object#collect_stats_from_users69
  0.000.000.000.001/500001Object#work115
NaN%NaN%0.000.000.000.00500001 + + Array#sort! + +
  0.000.000.000.001/1Object#work114
NaN%NaN%0.000.000.000.001 + + Array#size + +
  0.000.000.000.00500000/500001Object#collect_stats_from_users68
  0.000.000.000.001/500001Object#work115
NaN%NaN%0.000.000.000.00500001 + + Array#join + +
  0.000.000.000.001/1Object#work139
NaN%NaN%0.000.000.000.001 + + <Module::Process>#pid + +
  0.000.000.000.001/1Object#work93
NaN%NaN%0.000.000.000.001 + + <Class::IO>#open + +
  0.000.000.000.001/1File#initialize +
  0.000.000.000.001/1Object#work98
NaN%NaN%0.000.000.000.001 + + <Class::IO>#foreach + +
  0.000.000.000.002750940/8253020Array#<<102 +
  0.000.000.000.002750940/2750940Object#parse_session102 +
  0.000.000.000.002750940/3250940User#sessions102 +
  0.000.000.000.003250940/8752820Hash#[]=108 +
  0.000.000.000.003250940/8752820Integer#+108 +
  0.000.000.000.003250940/8752820Hash#[]108 +
  0.000.000.000.00500000/1000000User#attributes=107 +
  0.000.000.000.00500000/500000Object#parse_user107 +
  0.000.000.000.00500000/500001Object#collect_stats_from_users105 +
  0.000.000.000.003250940/19005641Array#[]100 +
  0.000.000.000.003250940/3250940String#split99 +
  0.000.000.000.003250940/3250940String#chop99 +
  0.000.000.000.002750940/3250940<Class::IO>#foreach
  0.000.000.000.00500000/3250940Object#collect_stats_from_users38
NaN%NaN%0.000.000.000.003250940 + + User#sessions + +
  0.000.000.000.001/1Class#new
NaN%NaN%0.000.000.000.001 + + User#initialize + + 11
  0.000.000.000.00500000/1000000<Class::IO>#foreach
  0.000.000.000.00500000/1000000Object#save_user82
NaN%NaN%0.000.000.000.001000000 + + User#attributes= + +
  0.000.000.000.00500001/1000001Object#collect_stats_from_users36
  0.000.000.000.00500000/1000001Object#save_user77
NaN%NaN%0.000.000.000.001000001 + + User#attributes + +
  0.000.000.000.003250940/3250940<Class::IO>#foreach
NaN%NaN%0.000.000.000.003250940 + + String#split + +
  0.000.000.000.003250940/3250940<Class::IO>#foreach
NaN%NaN%0.000.000.000.003250940 + + String#chop + +
  0.000.000.000.00500000/500000Object#collect_stats_from_users71
NaN%NaN%0.000.000.000.00500000 + + Object#save_user + + 74
  0.000.000.000.00500000/1000000User#attributes=82 +
  0.000.000.000.00500000/500000User#sessions=81 +
  0.000.000.000.00999999/1000005IO#write80 +
  0.000.000.000.00500000/500000Integer#-79 +
  0.000.000.000.00500000/500000String#length79 +
  0.000.000.000.001000000/1000000String#==79 +
  0.000.000.000.001500000/1500000String#[]79 +
  0.000.000.000.00500000/500001JSON::Ext::Generator::GeneratorMethods::Hash#to_json78 +
  0.000.000.000.00500000/1000001User#attributes77 +
  0.000.000.000.00500000/500000Integer#>75 +
  0.000.000.000.00500000/500000<Class::IO>#foreach
NaN%NaN%0.000.000.000.00500000 + + Object#parse_user + + 17
  0.000.000.000.001000000/1000000String#<<19 +
  0.000.000.000.002000000/19005641Array#[]19 +
  0.000.000.000.002750940/2750940<Class::IO>#foreach
NaN%NaN%0.000.000.000.002750940 + + Object#parse_session + + 25
  0.000.000.000.0013754700/19005641Array#[]27 +
  0.000.000.000.00500001/500001JSON::Ext::Generator::GeneratorMethods::Hash#to_json
NaN%NaN%0.000.000.000.00500001 + + JSON::Ext::Generator::State#initialize + +
  0.000.000.000.003250940/8752820<Class::IO>#foreach
  0.000.000.000.005501880/8752820Array#each
NaN%NaN%0.000.000.000.008752820 + + Integer#+ + +
  0.000.000.000.003/3Kernel#puts
NaN%NaN%0.000.000.000.003 + + IO#puts + +
  0.000.000.000.001/1Hash#to_s +
  0.000.000.000.003/1000005IO#write +
  0.000.000.000.003250940/8752820<Class::IO>#foreach
  0.000.000.000.005501880/8752820Array#each
NaN%NaN%0.000.000.000.008752820 + + Hash#[]= + +
  0.000.000.000.003250940/8752820<Class::IO>#foreach
  0.000.000.000.005501880/8752820Array#each
NaN%NaN%0.000.000.000.008752820 + + Hash#[] + +
  0.000.000.000.001/1<Class::IO>#open
NaN%NaN%0.000.000.000.001 + + File#initialize + +
  0.000.000.000.00500000/500000Object#collect_stats_from_users38
NaN%NaN%0.000.000.000.00500000 + + Enumerable#inject + +
  0.000.000.000.00500000/500000Array#each +
  0.000.000.000.00500000/500000Object#collect_stats_from_users68
NaN%NaN%0.000.000.000.00500000 + + Array#sort + +
  0.000.000.000.00500000/500000Object#collect_stats_from_users69
NaN%NaN%0.000.000.000.00500000 + + Array#reverse! + +
  0.000.000.000.00500000/500000Object#collect_stats_from_users62
NaN%NaN%0.000.000.000.00500000 + + Array#any? + +
  0.000.000.000.001431949/2075737String#=~62 +
  0.000.000.000.00500000/500000Object#collect_stats_from_users64
NaN%NaN%0.000.000.000.00500000 + + Array#all? + +
  0.000.000.000.00643788/2075737String#=~64 +
  0.000.000.000.002750940/8253020<Class::IO>#foreach
  0.000.000.000.005502080/8253020Array#each
NaN%NaN%0.000.000.000.008253020 + + Array#<< + +
  0.000.000.000.00500000/500000Object#save_user81
NaN%NaN%0.000.000.000.00500000 + + User#sessions= + +
  0.000.000.000.00500000/500000Object#save_user79
NaN%NaN%0.000.000.000.00500000 + + String#length + +
  0.000.000.000.001500000/1500000Object#save_user79
NaN%NaN%0.000.000.000.001500000 + + String#[] + +
  0.000.000.000.001431949/2075737Array#any?
  0.000.000.000.00643788/2075737Array#all?
NaN%NaN%0.000.000.000.002075737 + + String#=~ + +
  0.000.000.000.001000000/1000000Object#save_user79
NaN%NaN%0.000.000.000.001000000 + + String#== + +
  0.000.000.000.001000000/1000000Object#parse_user19
NaN%NaN%0.000.000.000.001000000 + + String#<< + +
  0.000.000.000.00500000/500000Object#save_user75
NaN%NaN%0.000.000.000.00500000 + + Integer#> + +
  0.000.000.000.00500000/500000Object#save_user79
NaN%NaN%0.000.000.000.00500000 + + Integer#- + +
  0.000.000.000.001/1IO#puts
NaN%NaN%0.000.000.000.001 + + Hash#to_s + +
  0.000.000.000.003/3Integer#inspect +
  0.000.000.000.005/5String#inspect +
  0.000.000.000.00500000/500000Enumerable#inject
NaN%NaN%0.000.000.000.00500000 + + Array#each + +
  0.000.000.000.002750940/2750940BasicObject#!57 +
  0.000.000.000.002750940/2750940Array#include?57 +
  0.000.000.000.005502080/8253020Array#<<55 +
  0.000.000.000.002750940/2750940String#upcase!55 +
  0.000.000.000.002750940/2750940Integer#<54 +
  0.000.000.000.002750940/2750941String#to_i52 +
  0.000.000.000.005501880/8752820Hash#[]=51 +
  0.000.000.000.005501880/8752820Integer#+51 +
  0.000.000.000.005501880/8752820Hash#[]51 +
  0.000.000.000.002750940/2750940Array#each
NaN%NaN%0.000.000.000.002750940 + + String#upcase! + +
  0.000.000.000.005/5Hash#to_s
NaN%NaN%0.000.000.000.005 + + String#inspect + +
  0.000.000.000.003/3Hash#to_s
NaN%NaN%0.000.000.000.003 + + Integer#inspect + +
  0.000.000.000.002750940/2750940Array#each
NaN%NaN%0.000.000.000.002750940 + + Integer#< + +
  0.000.000.000.002750940/2750940Array#each
NaN%NaN%0.000.000.000.002750940 + + BasicObject#! + +
  0.000.000.000.002750940/2750940Array#each
NaN%NaN%0.000.000.000.002750940 + + Array#include? + +
* indicates recursively called methods
+ +
+ + + + diff --git a/ruby_prof_reports/graphviz.dot b/ruby_prof_reports/graphviz.dot new file mode 100644 index 00000000..0d9b6a50 --- /dev/null +++ b/ruby_prof_reports/graphviz.dot @@ -0,0 +1,4 @@ +digraph "Profile" { +labelloc=t; +labeljust=l; +subgraph "Thread 260" { diff --git a/task-2.rb b/task-2.rb index 34e09a3c..9e1d30c4 100644 --- a/task-2.rb +++ b/task-2.rb @@ -6,7 +6,7 @@ require 'minitest/autorun' class User - attr_reader :attributes, :sessions + attr_accessor :attributes, :sessions def initialize(attributes:, sessions:) @attributes = attributes @@ -15,46 +15,110 @@ def initialize(attributes:, sessions:) end def parse_user(user) - fields = user.split(',') - parsed_result = { - 'id' => fields[1], - 'first_name' => fields[2], - 'last_name' => fields[3], - 'age' => fields[4], + { + 'id' => user[1], + 'full_name' => user[2] << ' ' << user[3], + 'age' => user[4], } end 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[1], + 'session_id' => session[2], + 'browser' => session[3], + 'time' => session[4], + 'date' => session[5], } 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)) +def collect_stats_from_user + return unless @user.attributes + + stats = @user.sessions.inject({ + # Собираем количество сессий по пользователям + 'sessionsCount' => 0, + # Собираем количество времени по пользователям + 'totalTime' => 0, + # Выбираем самую длинную сессию пользователя + 'longestSession' => 0, + # Браузеры пользователя через запятую + 'browsers' => [], + # Даты сессий через запятую в обратном порядке в формате iso8601 + 'dates' => [] + }) do |metric, session| + # Собираем количество сессий по пользователям + metric['sessionsCount'] += 1 + session_time = session['time'].to_i + metric['totalTime'] += session_time + metric['longestSession'] = session_time if metric['longestSession'] < session_time + metric['browsers'] << session['browser'].upcase! + metric['dates'] << session['date'] + @result_data['allBrowsers'] << session['browser'] if !@result_data['allBrowsers'].include?(session['browser']) + metric end + + # Хоть раз использовал IE? + stats['usedIE'] = stats['browsers'].any? { |b| b =~ /INTERNET EXPLORER/ } + # Всегда использовал только Chrome? + stats['alwaysUsedChrome'] = stats['browsers'].all? { |b| b =~ /CHROME/ } + + stats['totalTime'] = "#{stats['totalTime']} min." + stats['longestSession'] = "#{stats['longestSession']} min." + stats['browsers'] = stats['browsers'].sort.join(', ') + stats['dates'].sort!.reverse! + + save_user(stats) end -def work - file_lines = File.read('data.txt').split("\n") +def save_user(stats) + @result.write(',') if @result_data['totalUsers'] > 1 - users = [] - sessions = [] + user_hash = { @user.attributes['full_name'] => stats } + json = user_hash.to_json + json = json[1...json.length - 1] if json[0] == '{' && json[-1] == '}' + @result.write(json) + @user.sessions = [] + @user.attributes = nil +end + +def work(file_name: ARGV[0] || 'data_large.txt') + @result_data = { + 'totalUsers' => 0, + 'uniqueBrowsersCount' => 0, + 'totalSessions' => 0, + 'allBrowsers' => [], + } - 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' + @result = File.open('result.json', 'w') + @result.write('{"usersStats":{') + + @user = User.new(attributes: nil, sessions: []) + + File.foreach(file_name) do |line| + cols = line.chop.split(',') + case cols[0] + when 'session' + @user.sessions << parse_session(cols) + @result_data['totalSessions'] += 1 + when 'user' + collect_stats_from_user + + @user.attributes = parse_user(cols) + @result_data['totalUsers'] += 1 + end end + collect_stats_from_user + + @result_data['uniqueBrowsersCount'] = @result_data['allBrowsers'].size + @result_data['allBrowsers'] = @result_data['allBrowsers'].sort!.join(',') + + @result.write('},') + @result.write(@result_data.to_json.slice(1..-1).to_s) + @result.close + + puts 'Finish work' # Отчёт в json # - Сколько всего юзеров + # - Сколько всего уникальных браузеров + @@ -70,77 +134,7 @@ def work # - Всегда использовал только Хром? + # - даты сессий в порядке убывания через запятую + - 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 - - report['allBrowsers'] = - sessions - .map { |s| s['browser'] } - .map { |b| b.upcase } - .sort - .uniq - .join(',') - - # Статистика по пользователям - users_objects = [] - - 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 - - # Выбираем самую длинную сессию пользователя - 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 "MEMORY USAGE: %d MB" % (`ps -o rss= -p #{Process.pid}`.to_i / 1024) end