serialbench 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/benchmark.yml +125 -0
  3. data/.github/workflows/ci.yml +74 -0
  4. data/.rspec +4 -0
  5. data/Gemfile +34 -0
  6. data/README.adoc +592 -0
  7. data/Rakefile +63 -0
  8. data/exe/serialbench +6 -0
  9. data/lib/serialbench/benchmark_runner.rb +540 -0
  10. data/lib/serialbench/chart_generator.rb +821 -0
  11. data/lib/serialbench/cli.rb +438 -0
  12. data/lib/serialbench/memory_profiler.rb +31 -0
  13. data/lib/serialbench/result_formatter.rb +182 -0
  14. data/lib/serialbench/result_merger.rb +1201 -0
  15. data/lib/serialbench/serializers/base_serializer.rb +63 -0
  16. data/lib/serialbench/serializers/json/base_json_serializer.rb +67 -0
  17. data/lib/serialbench/serializers/json/json_serializer.rb +58 -0
  18. data/lib/serialbench/serializers/json/oj_serializer.rb +102 -0
  19. data/lib/serialbench/serializers/json/yajl_serializer.rb +67 -0
  20. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +76 -0
  21. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +55 -0
  22. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +50 -0
  23. data/lib/serialbench/serializers/xml/base_parser.rb +69 -0
  24. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +71 -0
  25. data/lib/serialbench/serializers/xml/libxml_parser.rb +98 -0
  26. data/lib/serialbench/serializers/xml/libxml_serializer.rb +127 -0
  27. data/lib/serialbench/serializers/xml/nokogiri_parser.rb +111 -0
  28. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +118 -0
  29. data/lib/serialbench/serializers/xml/oga_parser.rb +85 -0
  30. data/lib/serialbench/serializers/xml/oga_serializer.rb +125 -0
  31. data/lib/serialbench/serializers/xml/ox_parser.rb +64 -0
  32. data/lib/serialbench/serializers/xml/ox_serializer.rb +88 -0
  33. data/lib/serialbench/serializers/xml/rexml_parser.rb +129 -0
  34. data/lib/serialbench/serializers/xml/rexml_serializer.rb +121 -0
  35. data/lib/serialbench/serializers.rb +62 -0
  36. data/lib/serialbench/version.rb +5 -0
  37. data/lib/serialbench.rb +42 -0
  38. data/serialbench.gemspec +51 -0
  39. metadata +239 -0
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Serialbench
4
+ module Serializers
5
+ class BaseSerializer
6
+ def initialize
7
+ # Override in subclasses
8
+ end
9
+
10
+ def available?
11
+ raise NotImplementedError, 'Subclasses must implement #available?'
12
+ end
13
+
14
+ def name
15
+ raise NotImplementedError, 'Subclasses must implement #name'
16
+ end
17
+
18
+ def version
19
+ raise NotImplementedError, 'Subclasses must implement #version'
20
+ end
21
+
22
+ def format
23
+ raise NotImplementedError, 'Subclasses must implement #format'
24
+ end
25
+
26
+ def parse(data)
27
+ raise NotImplementedError, 'Subclasses must implement #parse'
28
+ end
29
+
30
+ def generate(object)
31
+ raise NotImplementedError, 'Subclasses must implement #generate'
32
+ end
33
+
34
+ def stream_parse(data, &block)
35
+ # Default implementation falls back to regular parsing
36
+ # Override in subclasses that support streaming
37
+ result = parse(data)
38
+ yield(:document, result) if block_given?
39
+ result
40
+ end
41
+
42
+ def supports_streaming?
43
+ # Override in subclasses that support streaming
44
+ false
45
+ end
46
+
47
+ protected
48
+
49
+ def require_library(library_name)
50
+ require library_name
51
+ true
52
+ rescue LoadError
53
+ false
54
+ end
55
+
56
+ def get_version(constant_path)
57
+ constant_path.split('::').reduce(Object) { |obj, const| obj.const_get(const) }
58
+ rescue NameError
59
+ 'unknown'
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Json
8
+ class BaseJsonSerializer < BaseSerializer
9
+ def format
10
+ :json
11
+ end
12
+
13
+ # JSON-specific methods
14
+ def parse_object(json_string)
15
+ parse(json_string)
16
+ end
17
+
18
+ def generate_json(object, options = {})
19
+ generate(object, options)
20
+ end
21
+
22
+ # JSON-specific features
23
+ def features
24
+ {
25
+ pretty_print: supports_pretty_print?,
26
+ streaming: supports_streaming?,
27
+ symbol_keys: supports_symbol_keys?,
28
+ custom_types: supports_custom_types?
29
+ }
30
+ end
31
+
32
+ protected
33
+
34
+ def supports_pretty_print?
35
+ true
36
+ end
37
+
38
+ def supports_symbol_keys?
39
+ false
40
+ end
41
+
42
+ def supports_custom_types?
43
+ false
44
+ end
45
+
46
+ # Subclasses should override this to specify their library name
47
+ def library_require_name
48
+ raise NotImplementedError, 'Subclasses must implement #library_require_name'
49
+ end
50
+
51
+ public
52
+
53
+ # Check if the JSON library is available
54
+ def available?
55
+ return @available if defined?(@available)
56
+
57
+ @available = begin
58
+ require library_require_name
59
+ true
60
+ rescue LoadError
61
+ false
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_json_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Json
8
+ class JsonSerializer < BaseJsonSerializer
9
+ def available?
10
+ require_library('json')
11
+ end
12
+
13
+ def name
14
+ 'json'
15
+ end
16
+
17
+ def version
18
+ require 'json'
19
+ JSON::VERSION
20
+ rescue LoadError, NameError
21
+ 'built-in'
22
+ end
23
+
24
+ def parse(json_string)
25
+ require 'json'
26
+ JSON.parse(json_string)
27
+ end
28
+
29
+ def generate(object, options = {})
30
+ require 'json'
31
+ if options[:pretty]
32
+ JSON.pretty_generate(object)
33
+ else
34
+ JSON.generate(object)
35
+ end
36
+ end
37
+
38
+ def supports_streaming?
39
+ false
40
+ end
41
+
42
+ protected
43
+
44
+ def supports_pretty_print?
45
+ true
46
+ end
47
+
48
+ def supports_symbol_keys?
49
+ false
50
+ end
51
+
52
+ def supports_custom_types?
53
+ false
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_json_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Json
8
+ class OjSerializer < BaseJsonSerializer
9
+ def available?
10
+ require_library('oj')
11
+ end
12
+
13
+ def name
14
+ 'oj'
15
+ end
16
+
17
+ def version
18
+ require 'oj'
19
+ Oj::VERSION
20
+ rescue LoadError, NameError
21
+ 'unknown'
22
+ end
23
+
24
+ def parse(json_string)
25
+ require 'oj'
26
+ Oj.load(json_string)
27
+ end
28
+
29
+ def generate(object, options = {})
30
+ require 'oj'
31
+ if options[:pretty]
32
+ Oj.dump(object, indent: 2)
33
+ else
34
+ Oj.dump(object)
35
+ end
36
+ end
37
+
38
+ def stream_parse(json_string, &block)
39
+ require 'oj'
40
+ # Oj supports streaming through saj (Simple API for JSON)
41
+ handler = StreamHandler.new(&block)
42
+ Oj.saj_parse(handler, json_string)
43
+ handler.result
44
+ rescue LoadError, NoMethodError
45
+ # Fallback to regular parsing if streaming not available
46
+ super
47
+ end
48
+
49
+ def supports_streaming?
50
+ require 'oj'
51
+ Oj.respond_to?(:saj_parse)
52
+ rescue LoadError
53
+ false
54
+ end
55
+
56
+ protected
57
+
58
+ def supports_pretty_print?
59
+ true
60
+ end
61
+
62
+ def supports_symbol_keys?
63
+ true
64
+ end
65
+
66
+ def supports_custom_types?
67
+ true
68
+ end
69
+ end
70
+
71
+ # Stream handler for Oj SAJ parsing
72
+ class StreamHandler
73
+ attr_reader :result
74
+
75
+ def initialize(&block)
76
+ @block = block
77
+ @result = nil
78
+ end
79
+
80
+ def hash_start(key)
81
+ @block&.call(:hash_start, key)
82
+ end
83
+
84
+ def hash_end(key)
85
+ @block&.call(:hash_end, key)
86
+ end
87
+
88
+ def array_start(key)
89
+ @block&.call(:array_start, key)
90
+ end
91
+
92
+ def array_end(key)
93
+ @block&.call(:array_end, key)
94
+ end
95
+
96
+ def add_value(value, key)
97
+ @block&.call(:value, { key: key, value: value })
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_json_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Json
8
+ class YajlSerializer < BaseJsonSerializer
9
+ def name
10
+ 'yajl'
11
+ end
12
+
13
+ def parse(json_string)
14
+ require 'yajl'
15
+ Yajl::Parser.parse(json_string)
16
+ end
17
+
18
+ def generate(data)
19
+ require 'yajl'
20
+ Yajl::Encoder.encode(data)
21
+ end
22
+
23
+ def parse_streaming(json_string, &block)
24
+ require 'yajl'
25
+
26
+ parser = Yajl::Parser.new
27
+ parser.on_parse_complete = block if block
28
+
29
+ # Parse the JSON string
30
+ result = parser.parse(json_string)
31
+
32
+ # Return number of top-level objects processed
33
+ case result
34
+ when Array
35
+ result.length
36
+ when Hash
37
+ 1
38
+ else
39
+ 1
40
+ end
41
+ end
42
+
43
+ def supports_streaming?
44
+ true
45
+ end
46
+
47
+ def version
48
+ return 'unknown' unless available?
49
+
50
+ require 'yajl'
51
+ # YAJL doesn't have a VERSION constant, try to get gem version
52
+ begin
53
+ Gem.loaded_specs['yajl-ruby']&.version&.to_s || 'unknown'
54
+ rescue StandardError
55
+ 'unknown'
56
+ end
57
+ end
58
+
59
+ protected
60
+
61
+ def library_require_name
62
+ 'yajl'
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Toml
8
+ class BaseTomlSerializer < BaseSerializer
9
+ def format
10
+ :toml
11
+ end
12
+
13
+ # TOML-specific methods
14
+ def parse_config(toml_string)
15
+ parse(toml_string)
16
+ end
17
+
18
+ def generate_toml(object, options = {})
19
+ generate(object, options)
20
+ end
21
+
22
+ # TOML-specific features
23
+ def features
24
+ {
25
+ comments: supports_comments?,
26
+ arrays_of_tables: supports_arrays_of_tables?,
27
+ inline_tables: supports_inline_tables?,
28
+ multiline_strings: supports_multiline_strings?
29
+ }
30
+ end
31
+
32
+ def supports_streaming?
33
+ # TOML is typically not streamed due to its structure
34
+ false
35
+ end
36
+
37
+ protected
38
+
39
+ def supports_comments?
40
+ false
41
+ end
42
+
43
+ def supports_arrays_of_tables?
44
+ true
45
+ end
46
+
47
+ def supports_inline_tables?
48
+ true
49
+ end
50
+
51
+ def supports_multiline_strings?
52
+ true
53
+ end
54
+
55
+ # Subclasses should override this to specify their library name
56
+ def library_require_name
57
+ raise NotImplementedError, 'Subclasses must implement #library_require_name'
58
+ end
59
+
60
+ public
61
+
62
+ # Check if the TOML library is available
63
+ def available?
64
+ return @available if defined?(@available)
65
+
66
+ @available = begin
67
+ require library_require_name
68
+ true
69
+ rescue LoadError
70
+ false
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_toml_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Toml
8
+ class TomlRbSerializer < BaseTomlSerializer
9
+ def available?
10
+ require_library('toml-rb')
11
+ end
12
+
13
+ def name
14
+ 'toml-rb'
15
+ end
16
+
17
+ def version
18
+ require 'toml-rb'
19
+ # toml-rb doesn't expose a VERSION constant, so we'll use gem version
20
+ Gem.loaded_specs['toml-rb']&.version&.to_s || 'unknown'
21
+ rescue LoadError, NameError
22
+ 'unknown'
23
+ end
24
+
25
+ def parse(toml_string)
26
+ require 'toml-rb'
27
+ TomlRB.parse(toml_string)
28
+ end
29
+
30
+ def generate(object, options = {})
31
+ require 'toml-rb'
32
+ TomlRB.dump(object)
33
+ end
34
+
35
+ protected
36
+
37
+ def supports_comments?
38
+ false
39
+ end
40
+
41
+ def supports_arrays_of_tables?
42
+ true
43
+ end
44
+
45
+ def supports_inline_tables?
46
+ true
47
+ end
48
+
49
+ def supports_multiline_strings?
50
+ true
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_toml_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Toml
8
+ class TomlibSerializer < BaseTomlSerializer
9
+ def name
10
+ 'tomlib'
11
+ end
12
+
13
+ def parse(toml_string)
14
+ require 'tomlib'
15
+ Tomlib.load(toml_string)
16
+ end
17
+
18
+ def generate(data)
19
+ require 'tomlib'
20
+ Tomlib.dump(data)
21
+ end
22
+
23
+ def parse_streaming(toml_string, &block)
24
+ # TOML doesn't typically support streaming parsing
25
+ # Parse the entire document and yield it
26
+ result = parse(toml_string)
27
+ block&.call(result) if block
28
+ 1 # Return 1 document processed
29
+ end
30
+
31
+ def supports_streaming?
32
+ false # TOML is typically parsed as a whole document
33
+ end
34
+
35
+ def version
36
+ return 'unknown' unless available?
37
+
38
+ require 'tomlib'
39
+ Tomlib::VERSION
40
+ end
41
+
42
+ protected
43
+
44
+ def library_require_name
45
+ 'tomlib'
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Serialbench
4
+ module Parsers
5
+ class BaseParser
6
+ attr_reader :name, :version
7
+
8
+ def initialize
9
+ @name = self.class.name.split('::').last.gsub('Parser', '').downcase
10
+ @version = detect_version
11
+ end
12
+
13
+ # Parse XML string into document object
14
+ def parse_dom(xml_string)
15
+ raise NotImplementedError, 'Subclasses must implement parse_dom'
16
+ end
17
+
18
+ # Parse XML with SAX-style streaming
19
+ def parse_sax(xml_string, handler = nil)
20
+ raise NotImplementedError, 'Subclasses must implement parse_sax'
21
+ end
22
+
23
+ # Generate XML string from document object
24
+ def generate_xml(document, options = {})
25
+ raise NotImplementedError, 'Subclasses must implement generate_xml'
26
+ end
27
+
28
+ # Check if library is available
29
+ def available?
30
+ require library_require_name
31
+ true
32
+ rescue LoadError
33
+ false
34
+ end
35
+
36
+ # Get library features
37
+ def features
38
+ {
39
+ xpath: supports_xpath?,
40
+ namespaces: supports_namespaces?,
41
+ validation: supports_validation?,
42
+ streaming: supports_streaming?
43
+ }
44
+ end
45
+
46
+ def supports_streaming?
47
+ false
48
+ end
49
+
50
+ protected
51
+
52
+ def detect_version
53
+ 'unknown'
54
+ end
55
+
56
+ def supports_xpath?
57
+ false
58
+ end
59
+
60
+ def supports_namespaces?
61
+ true
62
+ end
63
+
64
+ def supports_validation?
65
+ false
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base_serializer'
4
+
5
+ module Serialbench
6
+ module Serializers
7
+ module Xml
8
+ class BaseXmlSerializer < BaseSerializer
9
+ def format
10
+ :xml
11
+ end
12
+
13
+ # XML-specific methods
14
+ def parse_dom(xml_string)
15
+ parse(xml_string)
16
+ end
17
+
18
+ def parse_sax(xml_string, &block)
19
+ stream_parse(xml_string, &block)
20
+ end
21
+
22
+ def generate_xml(document, options = {})
23
+ generate(document, options)
24
+ end
25
+
26
+ # XML-specific features
27
+ def features
28
+ {
29
+ xpath: supports_xpath?,
30
+ namespaces: supports_namespaces?,
31
+ validation: supports_validation?,
32
+ streaming: supports_streaming?
33
+ }
34
+ end
35
+
36
+ protected
37
+
38
+ def supports_xpath?
39
+ false
40
+ end
41
+
42
+ def supports_namespaces?
43
+ true
44
+ end
45
+
46
+ def supports_validation?
47
+ false
48
+ end
49
+
50
+ # Subclasses should override this to specify their library name
51
+ def library_require_name
52
+ raise NotImplementedError, 'Subclasses must implement #library_require_name'
53
+ end
54
+
55
+ public
56
+
57
+ # Check if the XML library is available
58
+ def available?
59
+ return @available if defined?(@available)
60
+
61
+ @available = begin
62
+ require library_require_name
63
+ true
64
+ rescue LoadError
65
+ false
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end