Protox

repository·master·Indexed 43 days ago

https://github.com/ahamez/protox

An Elixir library for working with Google's Protocol Buffers (proto2 and proto3), providing tools for encoding, decoding, and code generation. It supports defining schemas via inlined strings or external files, generates Elixir structs for messages, and includes a CLI for source file generation. Requires Elixir >= 1.15, OTP >= 26, and protoc >= 3.0.

Tokens
9.2K
Snippets
35
Records
44
Agent score
54%

What's inside Protox

  1. Handle unknown fields

    master

    When decoding, Protox retains fields present on the wire that do not correspond to the protobuf definition (unknown fields). This ensures forward compatibility.

    To access these fields, use the unknown_fields/1 function on the decoded message. It returns a list of {tag, wire_type, bytes} tuples.

    # Accessing unknown fields
    msg = Msg.decode!(<<8, 42, 42, 4, 121, 97, 121, 101, 136, 241, 4, 83>>)
    fields = Msg.unknown_fields(msg)
    # Returns: [{5, 2, <<121, 97, 121, 101>>]}]
  2. Configure module namespaces

    master

    By default, the module in which use Protox is called is ignored in the generated module names. To nest generated modules under a specific prefix, use the namespace: option.

    Protox also honors the package directive in .proto files, converting package abc.def; to the Elixir module Abc.Def.

    defmodule Bar do
      use Protox, 
        schema: """
        syntax = "proto3";
        package abc;
        message Msg { int32 a = 1; }
        """,
        namespace: __MODULE__
    end
    
    # Resulting module: Bar.Abc.Msg
    msg = %Bar.Abc.Msg{a: 42}
  3. How to interpret profiling output

    master

    When reading profiling output, keep these two rules in mind:

    1. Hot functions are at the bottom: Rows are sorted ascending. Use tail instead of head to find the most impactful functions.
    2. Encoding vs. Decoding attribution:
      • Encode profiles provide per-field attribution (e.g., encode_<field>/2).
      • Decode profiles do not provide per-field attribution because the decoder uses a single self-recursive parse_key_value/2 function. To get a type-level breakdown for decoding, use --profile-scope codec combined with edge_* inputs to isolate specific paths.
  4. Update Protox macro options for v2

    master

    When using the Protox macro, note the following changes to configuration options:

    • :path is removed: Use the :paths option instead. It expects a list, so provide a list containing a single path: paths: ["path/to/schema.proto"].
    • :keep_unknown_fields is removed: Unknown fields are now always kept by default. If you do not want to process them, you must ignore them in your application logic.
  5. Manage the benchmark corpus

    master

    The benchmark corpus is stored in ./benchmark/benchmark_payloads.bin. It is a compressed :erlang.term_to_binary map of input_name => {module, [{message, size, bytes}]}.

    Commands:

    • Regenerate payloads: mix protox.benchmark.generate.payloads.
      • Warning: Generation is not seeded. Regenerating will produce a different corpus and make previous .benchee files incomparable.
    • Add a new .proto: Add the file to the list in compile_benchmark_protos.ex. This ensures the file is registered as an @external_resource and triggers a recompile on edits.
    mix protox.benchmark.generate.payloads
  6. Define Protobuf schemas in Elixir

    master

    You can define your protobuf schemas directly within an Elixir module using the Protox macro. This generates Elixir structs for your messages.

    Using an inlined schema

    Pass the schema string to the schema: option in the use Protox macro.

    Using external files

    Pass a list of file paths to the files: option.

    Specifying include paths

    Use the paths: option to specify directories where Protox should search for imports (equivalent to the -I option in protoc).

    # Inlined schema
    defmodule MyModule do
      use Protox, schema: """
      syntax = "proto3";
      message Baz { }
      message Foo {
        int32 a = 1;
        map<int32, Baz> b = 2;
      }
      """
    end
    
    # Using files and include paths
    defmodule Baz do
      use Protox,
        files: [
          "./defs1/prefix/foo.proto",
          "./defs1/prefix/bar.proto"
        ],
        paths: [
          "./defs1"
        ]
    end
  7. Install Protox

    master

    Add :protox to your mix.exs dependencies.

    Prerequisites:

    • Elixir >= 1.15 and OTP >= 26
    • protoc >= 3.0 must be available in your $PATH (required at compile-time only).
    def deps do
      [{:protox, "~> 2.0"}]
    end
  8. Launch the Protox benchmark

    master

    Run benchmarks for encode or decode tasks using the mix protox.benchmark.run command. You must provide a <BENCHMARK_TAG> to identify the run. Results are stored in ./benchmark/output/benchee/<TASK>-<HHMMSS>-<BENCHMARK_TAG>.benchee.

    Available Flags:

    • --task <TASK>: Specify encode or decode.
    • --warmup <N>: Number of warmup iterations (default: 2).
    • --time <N>: Duration of the measurement (default: 5).
    • --memory-time <N>: Duration of memory measurement (default: 2).
    • --reduction-time <N>: Duration of reduction measurement (default: 2).
    • --input <INPUT_NAME>: Restrict the run to a specific subset of the corpus (e.g., maps, test_all_types_proto3).
    # Short run (defaults)
    mix protox.benchmark.run --task decode my_tag
    
    # Longer run to reduce variance
    mix protox.benchmark.run --task encode --warmup 3 --time 15 --memory-time 5 --reduction-time 5 my_long_tag
    
    # Restrict to a specific input subset
    mix protox.benchmark.run --task decode --input maps,test_all_types_proto3 my_tag
  9. Aggregate benchmark results

    master

    Use mix protox.benchmark.report to compare benchmark runs. This command prints a comparison to the console and generates an HTML report in ./benchmark/output/html/.

    Note: Benchee matches scenarios across runs by input name. If you rename an input, the suites will be incomparable.

    mix protox.benchmark.report ./benchmark/output/benchee/encode-*
  10. Profile Protox code performance

    master

    Switch the benchmark task into profiling mode using the --profile-after flag. In this mode, Benchee runs each scenario exactly once under a profiler after the initial setup. Profiling runs do not save results to the .benchee output directory and do not compete with measurement runs.

    Profiling Flags:

    • --profile-after <PROFILER>: Selects the profiler. Supported values: tprof, cprof, eprof, fprof.
    • --profile-type <TYPE>: Selects the metric. Supported values: memory, calls, time (note: time is only for tprof). Default is memory.
    • --profile-scope <SCOPE>: Selects the scope. Supported values: module, codec, all (note: all is only for tprof).

    Recommended Profiler: Use tprof with --profile-type memory to report exact words allocated per function, or --profile-type calls to report exact call counts.

    mix protox.benchmark.run --task encode --profile-after tprof --profile-type memory --input synthetic_200