Merge pull request #36 from robertbaldyga/add-simple-example

Add simple ocf usage example
This commit is contained in:
Jan Musiał 2019-01-15 12:28:24 +01:00 committed by GitHub
commit b9fce50783
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 813 additions and 0 deletions

37
example/simple/Makefile Normal file
View File

@ -0,0 +1,37 @@
#
# Copyright(c) 2019 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause-Clear
#
OCFDIR=../../
SRCDIR=src/
INCDIR=include/
SRC=$(shell find ${SRCDIR} -name \*.c)
OBJS = $(patsubst %.c, %.o, $(SRC))
PROGRAM=simple
CC = gcc
CFLAGS = -g -Wall -I${INCDIR} -I${SRCDIR}/ocf/env/
LDFLAGS = -lm -lz -pthread
all: sync
$(MAKE) $(PROGRAM)
$(PROGRAM): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
sync:
@$(MAKE) -C ${OCFDIR} inc O=$(PWD)
@$(MAKE) -C ${OCFDIR} src O=$(PWD)
@$(MAKE) -C ${OCFDIR} env O=$(PWD) ENV=posix
clean:
@rm -rf $(PROGRAM) $(OBJS)
distclean:
@rm -rf $(PROGRAM) $(OBJS)
@rm -rf src/ocf
@rm -rf include/ocf
.PHONY: all clean

331
example/simple/src/ctx.c Normal file
View File

@ -0,0 +1,331 @@
/*
* Copyright(c) 2019 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause-Clear
*/
#include <execinfo.h>
#include <ocf/ocf.h>
#include "ocf_env.h"
#include "data.h"
#include "dobj.h"
#include "ctx.h"
#define PAGE_SIZE 4096
/*
* Allocate structure representing data for io operations.
*/
ctx_data_t *ctx_data_alloc(uint32_t pages)
{
struct dobj_data *data;
data = malloc(sizeof(*data));
data->ptr = malloc(pages * PAGE_SIZE);
data->offset = 0;
return data;
}
/*
* Free data structure.
*/
void ctx_data_free(ctx_data_t *ctx_data)
{
struct dobj_data *data = ctx_data;
if (!data)
return;
free(data->ptr);
free(data);
}
/*
* This function is supposed to set protection of data pages against swapping.
* Can be non-implemented if not needed.
*/
static int ctx_data_mlock(ctx_data_t *ctx_data)
{
return 0;
}
/*
* Stop protecting data pages against swapping.
*/
static void ctx_data_munlock(ctx_data_t *ctx_data)
{
}
/*
* Read data into flat memory buffer.
*/
static uint32_t ctx_data_rd(void *dst, ctx_data_t *src, uint32_t size)
{
struct dobj_data *data = src;
memcpy(dst, data->ptr + data->offset, size);
return size;
}
/*
* Write data from flat memory buffer.
*/
static uint32_t ctx_data_wr(ctx_data_t *dst, const void *src, uint32_t size)
{
struct dobj_data *data = dst;
memcpy(data->ptr + data->offset, src, size);
return size;
}
/*
* Fill data with zeros.
*/
static uint32_t ctx_data_zero(ctx_data_t *dst, uint32_t size)
{
struct dobj_data *data = dst;
memset(data->ptr + data->offset, 0, size);
return size;
}
/*
* Perform seek operation on data.
*/
static uint32_t ctx_data_seek(ctx_data_t *dst, ctx_data_seek_t seek,
uint32_t offset)
{
struct dobj_data *data = dst;
switch (seek) {
case ctx_data_seek_begin:
data->offset = offset;
break;
case ctx_data_seek_current:
data->offset += offset;
break;
}
return offset;
}
/*
* Copy data from one structure to another.
*/
static uint64_t ctx_data_cpy(ctx_data_t *dst, ctx_data_t *src,
uint64_t to, uint64_t from, uint64_t bytes)
{
struct dobj_data *data_dst = dst;
struct dobj_data *data_src = src;
memcpy(data_dst->ptr + to, data_src->ptr + from, bytes);
return bytes;
}
/*
* Perform secure erase of data (e.g. fill pages with zeros).
* Can be left non-implemented if not needed.
*/
static void ctx_data_secure_erase(ctx_data_t *ctx_data)
{
}
/*
* Initialize queue thread. To keep this example simple we handle queues
* synchronously, thus it's left non-implemented.
*/
static int ctx_queue_init(ocf_queue_t q)
{
return 0;
}
/*
* Trigger queue asynchronously. Made synchronous for simplicity.
*/
static inline void ctx_queue_kick_async(ocf_queue_t q)
{
ocf_queue_run(q);
}
/*
* Trigger queue synchronously. May be implemented as asynchronous as well,
* but in some environments kicking queue synchronously may reduce latency,
* so to take advantage of such situations OCF call synchronous variant of
* queue kick callback where possible.
*/
static void ctx_queue_kick_sync(ocf_queue_t q)
{
ocf_queue_run(q);
}
/*
* Stop queue thread. To keep this example simple we handle queues
* synchronously, thus it's left non-implemented.
*/
static void ctx_queue_stop(ocf_queue_t q)
{
}
/*
* Initialize cleaner thread. Cleaner thread is left non-implemented,
* to keep this example as simple as possible.
*/
static int ctx_cleaner_init(ocf_cleaner_t c)
{
return 0;
}
/*
* Stop cleaner thread. Cleaner thread is left non-implemented, to keep
* this example as simple as possible.
*/
static void ctx_cleaner_stop(ocf_cleaner_t c)
{
}
/*
* Initialize metadata updater thread. Metadata updater thread is left
* non-implemented to keep this example as simple as possible.
*/
static int ctx_metadata_updater_init(ocf_metadata_updater_t mu)
{
return 0;
}
/*
* Kick metadata updater thread. Metadata updater thread is left
* non-implemented to keep this example as simple as possible.
*/
static void ctx_metadata_updater_kick(ocf_metadata_updater_t mu)
{
}
/*
* Stop metadata updater thread. Metadata updater thread is left
* non-implemented to keep this example as simple as possible.
*/
static void ctx_metadata_updater_stop(ocf_metadata_updater_t mu)
{
}
/*
* This structure describes context ops. They are splitted into few categories:
* - data ops, providing context specific data handing interface,
* - queue ops, providing interface for starting, stoping and kicking
* queue thread in both synchronous and asynchronous way,
* - cleaner ops, providing interface to start and stop clener thread,
* - metadata updater ops, providing interface for starting, stoping
* and kicking metadata updater thread.
*/
static const struct ocf_ctx_ops ctx_ops = {
.name = "OCF Example",
.data_alloc = ctx_data_alloc,
.data_free = ctx_data_free,
.data_mlock = ctx_data_mlock,
.data_munlock = ctx_data_munlock,
.data_rd = ctx_data_rd,
.data_wr = ctx_data_wr,
.data_zero = ctx_data_zero,
.data_seek = ctx_data_seek,
.data_cpy = ctx_data_cpy,
.data_secure_erase = ctx_data_secure_erase,
.queue_init = ctx_queue_init,
.queue_kick_sync = ctx_queue_kick_sync,
.queue_kick = ctx_queue_kick_async,
.queue_stop = ctx_queue_stop,
.cleaner_init = ctx_cleaner_init,
.cleaner_stop = ctx_cleaner_stop,
.metadata_updater_init = ctx_metadata_updater_init,
.metadata_updater_kick = ctx_metadata_updater_kick,
.metadata_updater_stop = ctx_metadata_updater_stop,
};
/*
* Function prividing interface for printing to log used by OCF internals.
* It can handle differently messages at varous log levels.
*/
static int ctx_log_printf(const struct ocf_logger *logger,
ocf_logger_lvl_t lvl, const char *fmt, va_list args)
{
FILE *lfile = stdout;
if (lvl > log_info)
return 0;
if (lvl <= log_warn)
lfile = stderr;
return vfprintf(lfile, fmt, args);
}
#define CTX_LOG_TRACE_DEPTH 16
/*
* Function prividing interface for printing current stack. Used for debugging,
* and for providing additional information in log in case of errors.
*/
static int ctx_log_dump_stack(const struct ocf_logger *logger)
{
void *trace[CTX_LOG_TRACE_DEPTH];
char **messages = NULL;
int i, size;
size = backtrace(trace, CTX_LOG_TRACE_DEPTH);
messages = backtrace_symbols(trace, size);
printf("[stack trace]>>>\n");
for (i = 0; i < size; ++i)
printf("%s\n", messages[i]);
printf("<<<[stack trace]\n");
free(messages);
return 0;
}
/*
* Structure containng logger ops.
*/
static const struct ocf_logger logger = {
.printf = ctx_log_printf,
.dump_stack = ctx_log_dump_stack,
};
/*
* Function initializing context. Prepares context, sets logger and
* registers data object type.
*/
int ctx_init(ocf_ctx_t *ctx)
{
int ret;
ret = ocf_ctx_init(ctx, &ctx_ops);
if (ret)
return ret;
ocf_ctx_set_logger(*ctx, &logger);
ret = dobj_init(*ctx);
if (ret) {
dobj_cleanup(*ctx);
return ret;
}
return 0;
}
/*
* Function cleaning up context. Unregisters data object type and
* deinitializes context.
*/
void ctx_cleanup(ocf_ctx_t ctx)
{
dobj_cleanup(ctx);
ocf_ctx_exit(ctx);
}

19
example/simple/src/ctx.h Normal file
View File

@ -0,0 +1,19 @@
/*
* Copyright(c) 2019 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause-Clear
*/
#ifndef __CTX_H__
#define __CTX_H__
#include <ocf/ocf.h>
#define OBJ_TYPE 1
ctx_data_t *ctx_data_alloc(uint32_t pages);
void ctx_data_free(ctx_data_t *ctx_data);
int ctx_init(ocf_ctx_t *ocf_ctx);
void ctx_cleanup(ocf_ctx_t ctx);
#endif

14
example/simple/src/data.h Normal file
View File

@ -0,0 +1,14 @@
/*
* Copyright(c) 2019 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause-Clear
*/
#ifndef __DATA_H__
#define __DATA_H__
struct dobj_data {
void *ptr;
int offset;
};
#endif

168
example/simple/src/dobj.c Normal file
View File

@ -0,0 +1,168 @@
/*
* Copyright(c) 2019 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause-Clear
*/
#include <ocf/ocf.h>
#include "dobj.h"
#include "data.h"
#include "ctx.h"
#define DOBJ_SIZE 200*1024*1024
/*
* In open() function we store uuid data as object name (for debug messages)
* and allocate 200 MiB of memory to simulate backend storage device.
*/
static int dobj_open(ocf_data_obj_t obj)
{
const struct ocf_data_obj_uuid *uuid = ocf_dobj_get_uuid(obj);
struct dobj *dobj = ocf_dobj_get_priv(obj);
dobj->name = ocf_uuid_to_str(uuid);
dobj->mem = malloc(DOBJ_SIZE);
printf("DOBJ OPEN: (name: %s)\n", dobj->name);
return 0;
}
/*
* In close() function we just free memory allocated in open().
*/
static void dobj_close(ocf_data_obj_t obj)
{
struct dobj *dobj = ocf_dobj_get_priv(obj);
printf("DOBJ CLOSE: (name: %s)\n", dobj->name);
free(dobj->mem);
}
/*
* In submit_io() function we simulate read or write to backend storage device
* by doing memcpy() to or from previously allocated memory buffer.
*/
static void dobj_submit_io(struct ocf_io *io)
{
struct dobj_data *data;
struct dobj *dobj;
data = ocf_io_get_data(io);
dobj = ocf_dobj_get_priv(io->obj);
if (io->dir == OCF_WRITE) {
memcpy(dobj->mem + io->addr,
data->ptr + data->offset, io->bytes);
} else {
memcpy(data->ptr + data->offset,
dobj->mem + io->addr, io->bytes);
}
printf("DOBJ: (name: %s), IO: (dir: %s, addr: %ld, bytes: %d)\n",
dobj->name, io->dir == OCF_READ ? "read" : "write",
io->addr, io->bytes);
io->end(io, 0);
}
/*
* We don't need to implement submit_flush(). Just complete io with success.
*/
static void dobj_submit_flush(struct ocf_io *io)
{
io->end(io, 0);
}
/*
* We don't need to implement submit_discard(). Just complete io with success.
*/
static void dobj_submit_discard(struct ocf_io *io)
{
io->end(io, 0);
}
/*
* Let's set maximum io size to 128 KiB.
*/
static unsigned int dobj_get_max_io_size(ocf_data_obj_t obj)
{
return 128 * 1024;
}
/*
* Return data object size.
*/
static uint64_t dobj_get_length(ocf_data_obj_t obj)
{
return DOBJ_SIZE;
}
/*
* In set_data() we just assing data and offset to io.
*/
static int dobj_io_set_data(struct ocf_io *io, ctx_data_t *data,
uint32_t offset)
{
struct dobj_io *dobj_io = ocf_io_get_priv(io);
dobj_io->data = data;
dobj_io->offset = offset;
return 0;
}
/*
* In get_data() return data stored in io.
*/
static ctx_data_t *dobj_io_get_data(struct ocf_io *io)
{
struct dobj_io *dobj_io = ocf_io_get_priv(io);
return dobj_io->data;
}
/*
* This structure contains data object properties. It describes data object
* type, which can be later instantiated as backend storage object for cache
* or core.
*/
const struct ocf_data_obj_properties dobj_properties = {
.name = "Example dobj",
.io_priv_size = sizeof(struct dobj_io),
.dobj_priv_size = sizeof(struct dobj),
.caps = {
.atomic_writes = 0,
},
.ops = {
.open = dobj_open,
.close = dobj_close,
.submit_io = dobj_submit_io,
.submit_flush = dobj_submit_flush,
.submit_discard = dobj_submit_discard,
.get_max_io_size = dobj_get_max_io_size,
.get_length = dobj_get_length,
},
.io_ops = {
.set_data = dobj_io_set_data,
.get_data = dobj_io_get_data,
},
};
/*
* This function registers data object type in OCF context.
* It should be called just after context initialization.
*/
int dobj_init(ocf_ctx_t ocf_ctx)
{
return ocf_ctx_register_data_obj_type(ocf_ctx, OBJ_TYPE,
&dobj_properties);
}
/*
* This function unregisters data object type in OCF context.
* It should be called just before context cleanup.
*/
void dobj_cleanup(ocf_ctx_t ocf_ctx)
{
ocf_ctx_unregister_data_obj_type(ocf_ctx, OBJ_TYPE);
}

27
example/simple/src/dobj.h Normal file
View File

@ -0,0 +1,27 @@
/*
* Copyright(c) 2019 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause-Clear
*/
#ifndef __DOBJ_H__
#define __DOBJ_H__
#include <ocf/ocf.h>
#include "ocf_env.h"
#include "ctx.h"
#include "data.h"
struct dobj_io {
struct dobj_data *data;
uint32_t offset;
};
struct dobj {
uint8_t *mem;
const char *name;
};
int dobj_init(ocf_ctx_t ocf_ctx);
void dobj_cleanup(ocf_ctx_t ocf_ctx);
#endif

217
example/simple/src/main.c Normal file
View File

@ -0,0 +1,217 @@
/*
* Copyright(c) 2019 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause-Clear
*/
#include <stdio.h>
#include <stdlib.h>
#include <ocf/ocf.h>
#include "data.h"
#include "ctx.h"
/*
* Helper function for error handling.
*/
void error(char *msg)
{
printf("ERROR: %s", msg);
exit(1);
}
/*
* Function starting cache and attaching cache device.
*/
int initialize_cache(ocf_ctx_t ctx, ocf_cache_t *cache)
{
struct ocf_mngt_cache_config cache_cfg = { };
struct ocf_mngt_cache_device_config device_cfg = { };
int ret;
/* Cache configuration */
cache_cfg.backfill.max_queue_size = 65536;
cache_cfg.backfill.queue_unblock_size = 60000;
cache_cfg.cache_line_size = ocf_cache_line_size_4;
cache_cfg.cache_mode = ocf_cache_mode_wt;
cache_cfg.metadata_volatile = true;
cache_cfg.io_queues = 1;
cache_cfg.name = "cache1";
/* Cache deivce (data object) configuration */
device_cfg.data_obj_type = OBJ_TYPE;
device_cfg.cache_line_size = ocf_cache_line_size_4;
ret = ocf_uuid_set_str(&device_cfg.uuid, "cache");
if (ret)
return ret;
/* Start cache */
ret = ocf_mngt_cache_start(ctx, cache, &cache_cfg);
if (ret)
return ret;
/* Attach data object to cache */
ret = ocf_mngt_cache_attach(*cache, &device_cfg);
if (ret) {
ocf_mngt_cache_stop(*cache);
return ret;
}
return 0;
}
/*
* Function adding cache to core.
*/
int initialize_core(ocf_cache_t cache, ocf_core_t *core)
{
struct ocf_mngt_core_config core_cfg = { };
int ret;
/* Core configuration */
core_cfg.data_obj_type = OBJ_TYPE;
core_cfg.name = "core1";
ret = ocf_uuid_set_str(&core_cfg.uuid, "core");
if (ret)
return ret;
/* Add core to cache */
return ocf_mngt_cache_add_core(cache, core, &core_cfg);
}
/*
* Callback function called when write completes.
*/
void complete_write(struct ocf_io *io, int error)
{
struct dobj_data *data = ocf_io_get_data(io);
printf("WRITE COMPLETE: (error: %d)\n", error);
/* Free data buffer and io */
ctx_data_free(data);
ocf_io_put(io);
}
/*
* Callback function called when read completes.
*/
void complete_read(struct ocf_io *io, int error)
{
struct dobj_data *data = ocf_io_get_data(io);
printf("WRITE COMPLETE (error: %d)\n", error);
printf("DATA: \"%s\"\n", (char *)data->ptr);
/* Free data buffer and io */
ctx_data_free(data);
ocf_io_put(io);
}
/*
* Wrapper function for io submition.
*/
int submit_io(ocf_core_t core, struct dobj_data *data,
uint64_t addr, uint64_t len, int dir, ocf_end_io_t cmpl)
{
struct ocf_io *io;
/* Allocate new io */
io = ocf_core_new_io(core);
if (!io)
return -ENOMEM;
/* Setup io address, lenght, direction, flags and ioclass */
ocf_io_configure(io, addr, len, dir, 0, 0);
/* Assign data to io */
ocf_io_set_data(io, data, 0);
/* Setup completion function */
ocf_io_set_cmpl(io, NULL, NULL, cmpl);
/* Submit io */
ocf_core_submit_io(io);
return 0;
}
/*
* This function simulates actual business logic.
*
* It performs following steps:
* 1. Allocate data buffer for write and write it with example data.
* 2. Allocate new io, configure it for write, setup completion callback
* and perform write to the core.
* 3. Wait for write io completion (write is handled synchronosly, so no
* actual wait is needed, but in real life we would need to use some
* synchronization to be sure, that completion function has been already
* called). Alternatively we could issue read io from write completion
* callback.
* 4. Allocate data buffer for read.
* 5. Allocate new io, configure it for read, setup completion callback
* and perform read from the core, from the same address where data
* was previously written.
* 6. Print example data in read completion callback.
*
* Data buffers and ios are freed in completion callbacks, so there is no
* need to handle freeing in this function.
*/
void perform_workload(ocf_core_t core)
{
struct dobj_data *data1, *data2;
/* Allocate data buffer and fill it with example data */
data1 = ctx_data_alloc(1);
if (!data1)
error("Unable to allocate data1\n");
strcpy(data1->ptr, "This is some test data");
/* Prepare and submit write IO to the core */
submit_io(core, data1, 0, 512, OCF_WRITE, complete_write);
/* After write completes, complete_write() callback will be called. */
/*
* Here we would need to wait until write completes to be sure, that
* performing read we retrive written data.
*/
/* Allocate data buffer for read */
data2 = ctx_data_alloc(1);
if (!data2)
error("Unable to allocate data2\n");
/* Prepare and submit read IO to the core */
submit_io(core, data2, 0, 512, OCF_READ, complete_read);
/* After read completes, complete_read() callback will be called,
* where we print our example data to stdout.
*/
}
int main(int argc, char *argv[])
{
ocf_ctx_t ctx;
ocf_cache_t cache1;
ocf_core_t core1;
/* Initialize OCF context */
if (ctx_init(&ctx))
error("Unable to initialize context\n");
/* Start cache */
if (initialize_cache(ctx, &cache1))
error("Unable to start cache\n");
/* Add core */
if (initialize_core(cache1, &core1))
error("Unable to add core\n");
/* Do some actual io operations */
perform_workload(core1);
/* Remove core from cache */
if (ocf_mngt_cache_remove_core(cache1, ocf_core_get_id(core1), false))
error("Unable to remove core\n");
/* Stop cache */
if (ocf_mngt_cache_stop(cache1))
error("Unable to stop cache\n");
/* Deinitialize context */
ctx_cleanup(ctx);
return 0;
}