Write barman profile data to custom storage

This task describes how to interface with, and instruct, barman to store (or stream) captured data to storage of your choice.

Procedure

  1. To configure barman to use an in-memory buffer, follow the steps in Configuring Barman .

    This step generates the barman.c and barman.h files. The following steps override the backend selected in the configuration tool.

    When initializing the barman library, use the barman_initialize_with_user_supplied function instead of barman_initialize.

    The first argument passed to barman_initialize_with_user_supplied gets passed directly to the barman_ext_streaming_backend_init function shown in the following example.

  2. Prepare your code by implementing the functions described in the barman-ext-streaming-backend.h header file that is available on GitHub:

    For example, you can implement the functions as:

    /**
     * Initialize the backend.
     *
     * @param config    Pointer to some configuration data.
     * @return          True if successful.
     */
     bm_bool barman_ext_streaming_backend_init(void * config);
    
    /**
     * Write data as a frame.
     *
     * @param data    Data to write in the frame.
     * @param length  Length of the frame.
     * @param channel Channel to write the frame on.
     * @param flush_header   Set to BM_TRUE when the frame contains an update.
     * to the header. Indicates to flush the channel after writing the frame.
     */
     void barman_ext_streaming_backend_write_frame(const bm_uint8 * data, bm_uintptr length, bm_uint16 channel, bm_bool flush_header);
    
    /**
     * Shutdown the backend.
     */
     void barman_ext_streaming_backend_close(void);
    
    /**
     * Get the channel bank.
     *
     * If banked by a core this is just the core number.
     * If not banked by a core, this should always be 0.
     *
     * @return The bank
     */
     bm_uint32 barman_ext_streaming_backend_get_bank(void);
    
    • The function barman_ext_streaming_backend_init must perform whatever necessary setup (for example, opening the data file), and returns BM_TRUE on success, or BM_FALSE on failure.

    • The function barman_ext_streaming_backend_close must be called when the capture is disabled, and used to close any relevant storage (for example, by closing the data file).

    • The function barman_ext_streaming_backend_get_bank must return barman_get_core_no() in a multicore system, or return 0:

      bm_uint32 barman_ext_streaming_backend_get_bank(void)
      {
          return barman_get_core_no();
      }
      
    • The function barman_ext_streaming_backend_write_frame must be called for each data record, and store the records to the data store according to the following pseudocode:

      {
         /* Note: When the host has multiple cores or threads, so that it is
          * possible for multiple cores to call this function in parallel,
          * then you must ensure to synchronize here.
          */
      
          if (flush_header) {
              assert(data_store_is_empty || (previous_header_length == length));
             /** Write (or overwrite) the existing header at the start of the
               * data store.
              */
              write_blob_to_data_store_at_offset_zero(data, length);
          } else {
              assert(received_header);
      
             /** You must prefix the data record with a length field. Note:
              * This following example code produces a little-endian length,
              * but for big-endian targets this must be changed.
              */
              bm_uint8 length_header[8] = {
                  length >> (0 * 8),
                  length >> (1 * 8),
                  length >> (2 * 8),
                  length >> (3 * 8),
                  length >> (4 * 8),
                  length >> (5 * 8),
                  length >> (6 * 8),
                  length >> (7 * 8),
              };
      
             /** Append the size, which depends on if the target is 32-bit
              * (4-bytes) or 64-bit (8-bytes), to the end of the data store.
              */
              write_blob_to_end_of_data_store(length_header, sizeof(bm_uintptr));
      
             /** Now append the data to the end of the data store.
              */
              write_blob_to_end_of_data_store(data, length);
          }
      }
      

    For example, on a POSIX-like API, you could implement write_blob_to_data_store_at_offset_zero and write_blob_to_end_of_data_store as:

    static int file_handle;
    static bm_uintptr header_length = 0;
    
    static void write_blob(const bm_uint8 * data, bm_uintptr length)
    {
        while (length > 0)
        {
            int result = write(file_handle, data, length);
    
            assert(result > 0);
            length -= result;
            data += result;
        }
    }
    
    static void write_blob_to_end_of_data_store(const bm_uint8 * data, bm_uintptr length)
    {
        off_t new_offset = lseek(file_handle, 0, SEEK_END);
        assert((new_offset > 0) && (new_offset >= header_length));
        write_blob(data, length);
    }
    
    static void write_blob_to_data_store_at_offset_zero(const bm_uint8 * data, bm_uintptr length)
    {
        assert((header_length == 0) || (header_length == length));
        header_length = length;
        off_t new_offset = lseek(file_handle, 0, SEEK_SET);
    
        assert(new_offset == 0);
    
        write_blob(data, length);
        new_offset = lseek(file_handle, 0, SEEK_END);
    
        assert(new_offset >= length);
    }
    
  3. Compile your application with the following options:

    • -DBM_CONFIG_USE_DATASTORE=BM_CONFIG_USE_DATASTORE_STREAMING_USER_SUPPLIED - Sets a user-supplied datastore.

    • -DBM_CONFIG_STREAMING_DATASTORE_USER_SUPPLIED_NUMBER_OF_BANKS=<n> - Passes the number of banks in the user-supplied datastore. <n> is the maximum number of CPUs on the system. In other words, one greater than the largest value returned by barman_get_core_no().

    • -DBM_CONFIG_STREAMING_DATASTORE_USER_SUPPLIED_NUMBER_OF_CHANNELS=<n> - Passes the number of channels in the user-supplied datastore. <n> is a number greater than, or equal to, 1, and is the number of buffers per CPU to use for temporary storage.

    ./<compiler-command> <options> -DBM_CONFIG_USE_DATASTORE=BM_CONFIG_USE_DATASTORE_STREAMING_USER_SUPPLIED -DBM_CONFIG_STREAMING_DATASTORE_USER_SUPPLIED_NUMBER_OF_BANKS=<n> -DBM_CONFIG_STREAMING_DATASTORE_USER_SUPPLIED_NUMBER_OF_CHANNELS=<n> <source>