Type: fix Change-Id: If59a66aae658dd35dbcb4987ab00c306b3c6e2e2 Signed-off-by: Damjan Marion <damarion@cisco.com>
498 lines
13 KiB
C
498 lines
13 KiB
C
/*
|
|
* Copyright (c) 2017 Cisco and/or its affiliates.
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <vppinfra/maplog.h>
|
|
|
|
/**
|
|
* @brief Initialize a maplog object
|
|
*
|
|
* Compute record and file size parameters
|
|
* Create and map two log segments to seed the process
|
|
*
|
|
* @param[in/out] a init args structure
|
|
* @return 0 => success, <0 => failure
|
|
*/
|
|
__clib_export int
|
|
clib_maplog_init (clib_maplog_init_args_t *a)
|
|
{
|
|
int i, fd, limit;
|
|
int rv = 0;
|
|
u8 zero = 0;
|
|
u32 record_size_in_cache_lines;
|
|
u64 file_size_in_records;
|
|
clib_maplog_main_t *mm;
|
|
clib_maplog_header_t _h, *h = &_h;
|
|
|
|
ASSERT (a && a->mm);
|
|
mm = a->mm;
|
|
|
|
/* Already initialized? */
|
|
if (mm->flags & CLIB_MAPLOG_FLAG_INIT)
|
|
return (-2);
|
|
|
|
clib_memset (mm, 0, sizeof (*mm));
|
|
|
|
record_size_in_cache_lines =
|
|
(a->record_size_in_bytes + CLIB_CACHE_LINE_BYTES -
|
|
1) / CLIB_CACHE_LINE_BYTES;
|
|
|
|
file_size_in_records = a->file_size_in_bytes
|
|
/ (record_size_in_cache_lines * CLIB_CACHE_LINE_BYTES);
|
|
|
|
/* Round up file size in records to a power of 2, for speed... */
|
|
mm->log2_file_size_in_records = max_log2 (file_size_in_records);
|
|
file_size_in_records = 1ULL << (mm->log2_file_size_in_records);
|
|
a->file_size_in_bytes = file_size_in_records * record_size_in_cache_lines
|
|
* CLIB_CACHE_LINE_BYTES;
|
|
|
|
mm->file_basename = format (0, "%s", a->file_basename);
|
|
if (vec_len (mm->file_basename) > ARRAY_LEN (h->file_basename))
|
|
{
|
|
vec_free (mm->file_basename);
|
|
return -11;
|
|
}
|
|
|
|
mm->file_size_in_records = file_size_in_records;
|
|
mm->flags |= CLIB_MAPLOG_FLAG_INIT;
|
|
mm->record_size_in_cachelines = record_size_in_cache_lines;
|
|
limit = 2;
|
|
if (a->maplog_is_circular)
|
|
{
|
|
mm->log2_file_size_in_records = 63;
|
|
mm->flags |= CLIB_MAPLOG_FLAG_CIRCULAR;
|
|
limit = 1;
|
|
}
|
|
|
|
/*
|
|
* Map the one and only file for a circular log,
|
|
* two files for a normal log.
|
|
*/
|
|
for (i = 0; i < limit; i++)
|
|
{
|
|
mm->filenames[i] = format (0, "%v_%d", mm->file_basename,
|
|
mm->current_file_index++);
|
|
vec_add1 (mm->filenames[i], 0);
|
|
|
|
fd = open ((char *) mm->filenames[i], O_CREAT | O_RDWR | O_TRUNC, 0600);
|
|
if (fd < 0)
|
|
{
|
|
rv = -3;
|
|
goto fail;
|
|
}
|
|
|
|
if (lseek (fd, a->file_size_in_bytes - 1, SEEK_SET) == (off_t) - 1)
|
|
{
|
|
rv = -4;
|
|
goto fail;
|
|
}
|
|
if (write (fd, &zero, 1) != 1)
|
|
{
|
|
rv = -5;
|
|
goto fail;
|
|
}
|
|
|
|
mm->file_baseva[i] = mmap (0, a->file_size_in_bytes,
|
|
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (mm->file_baseva[i] == (u8 *) MAP_FAILED)
|
|
{
|
|
clib_unix_warning ("mmap");
|
|
goto fail;
|
|
}
|
|
(void) close (fd);
|
|
}
|
|
|
|
clib_memset (h, 0, sizeof (*h));
|
|
h->maplog_major_version = MAPLOG_MAJOR_VERSION;
|
|
h->maplog_minor_version = MAPLOG_MINOR_VERSION;
|
|
h->maplog_patch_version = MAPLOG_PATCH_VERSION;
|
|
h->application_id = a->application_id;
|
|
h->application_major_version = a->application_major_version;
|
|
h->application_minor_version = a->application_minor_version;
|
|
h->application_patch_version = a->application_patch_version;
|
|
h->record_size_in_cachelines = record_size_in_cache_lines;
|
|
h->cacheline_size = CLIB_CACHE_LINE_BYTES;
|
|
h->file_size_in_records = file_size_in_records;
|
|
h->number_of_records = ~0ULL;
|
|
h->number_of_files = ~0ULL;
|
|
h->maplog_flag_circular = a->maplog_is_circular;
|
|
memcpy (h->file_basename, mm->file_basename, vec_len (mm->file_basename));
|
|
|
|
mm->header_filename = format (0, "%v_header", mm->file_basename);
|
|
vec_add1 (mm->header_filename, 0);
|
|
|
|
fd = open ((char *) mm->header_filename, O_CREAT | O_RDWR | O_TRUNC, 0600);
|
|
if (fd < 0)
|
|
{
|
|
clib_unix_warning ("header create");
|
|
rv = -6;
|
|
goto fail;
|
|
}
|
|
rv = write (fd, h, sizeof (*h));
|
|
if (rv != sizeof (*h))
|
|
{
|
|
clib_unix_warning ("header write");
|
|
rv = -7;
|
|
goto fail;
|
|
}
|
|
(void) close (fd);
|
|
return 0;
|
|
|
|
fail:
|
|
if (fd >= 0)
|
|
(void) close (fd);
|
|
|
|
for (i = 0; i < limit; i++)
|
|
{
|
|
if (mm->file_baseva[i])
|
|
(void) munmap ((u8 *) mm->file_baseva[i], a->file_size_in_bytes);
|
|
if (mm->filenames[i])
|
|
(void) unlink ((char *) mm->filenames[i]);
|
|
vec_free (mm->filenames[i]);
|
|
}
|
|
if (mm->header_filename)
|
|
{
|
|
(void) unlink ((char *) mm->header_filename);
|
|
vec_free (mm->header_filename);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/* slow path: unmap a full log segment, and replace it */
|
|
|
|
__clib_export u8 *
|
|
_clib_maplog_get_entry_slowpath (clib_maplog_main_t *mm, u64 my_record_index)
|
|
{
|
|
int fd;
|
|
u8 *rv;
|
|
u8 zero = 0;
|
|
u32 unmap_index = (mm->current_file_index) & 1;
|
|
u64 file_size_in_bytes = mm->file_size_in_records
|
|
* mm->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES;
|
|
|
|
/* This should never happen */
|
|
ASSERT ((mm->flags & CLIB_MAPLOG_FLAG_CIRCULAR) == 0);
|
|
|
|
/*
|
|
* Kill some time by calling format before we make the previous log
|
|
* segment disappear. Obviously it won't do to call clib_maplog_get_entry(),
|
|
* wait 100ms, and then fill in the log entry.
|
|
*/
|
|
vec_reset_length (mm->filenames[unmap_index]);
|
|
mm->filenames[unmap_index] = format (mm->filenames[unmap_index],
|
|
"%v_%d", mm->file_basename,
|
|
mm->current_file_index++);
|
|
|
|
/* Unmap the previous (full) segment */
|
|
(void) munmap ((u8 *) mm->file_baseva[unmap_index], file_size_in_bytes);
|
|
|
|
/* Create a new segment */
|
|
fd = open ((char *) mm->filenames[unmap_index],
|
|
O_CREAT | O_RDWR | O_TRUNC, 0600);
|
|
|
|
/* This is not real error recovery... */
|
|
if (fd < 0)
|
|
{
|
|
clib_unix_warning ("creat");
|
|
abort ();
|
|
}
|
|
|
|
if (lseek (fd, file_size_in_bytes - 1, SEEK_SET) == (off_t) - 1)
|
|
{
|
|
clib_unix_warning ("lseek");
|
|
abort ();
|
|
}
|
|
if (write (fd, &zero, 1) != 1)
|
|
{
|
|
clib_unix_warning ("set-size write");
|
|
abort ();
|
|
}
|
|
|
|
mm->file_baseva[unmap_index] =
|
|
mmap (0, file_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (mm->file_baseva[unmap_index] == (u8 *) MAP_FAILED)
|
|
{
|
|
clib_unix_warning ("mmap");
|
|
abort ();
|
|
}
|
|
(void) close (fd);
|
|
|
|
rv = (u8 *)
|
|
mm->file_baseva[(my_record_index >> mm->log2_file_size_in_records) & 1] +
|
|
(my_record_index & (mm->file_size_in_records - 1))
|
|
* mm->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES;
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* @brief Update a mapped log header file
|
|
*
|
|
* Read the log header. Update the number of records, and number of files
|
|
* @param[in/out] mm mapped log object
|
|
*/
|
|
void
|
|
clib_maplog_update_header (clib_maplog_main_t * mm)
|
|
{
|
|
int fd, rv;
|
|
clib_maplog_header_t _h, *h = &_h;
|
|
|
|
if (!(mm->flags & CLIB_MAPLOG_FLAG_INIT))
|
|
return;
|
|
|
|
/* Open the log header */
|
|
fd = open ((char *) mm->header_filename, O_RDWR, 0600);
|
|
if (fd < 0)
|
|
{
|
|
clib_unix_warning ("reopen maplog header");
|
|
goto out;
|
|
}
|
|
|
|
/* Read the log */
|
|
rv = read (fd, h, sizeof (*h));
|
|
if (rv != sizeof (*h))
|
|
{
|
|
clib_unix_warning ("read maplog header");
|
|
goto out;
|
|
}
|
|
/* Fix the header... */
|
|
h->number_of_records = mm->next_record_index;
|
|
h->number_of_files = mm->current_file_index;
|
|
h->maplog_flag_wrapped = (mm->flags & CLIB_MAPLOG_FLAG_WRAPPED) ? 1 : 0;
|
|
|
|
/* Back to the beginning of the log header... */
|
|
if (lseek (fd, 0, SEEK_SET) < 0)
|
|
{
|
|
clib_unix_warning ("lseek to rewrite header");
|
|
goto out;
|
|
}
|
|
/* Rewrite the log header */
|
|
rv = write (fd, h, sizeof (*h));
|
|
if (rv != sizeof (*h))
|
|
clib_unix_warning ("rewrite header");
|
|
|
|
out:
|
|
if (fd >= 0)
|
|
(void) close (fd);
|
|
}
|
|
|
|
/**
|
|
* @brief Close a mapped log, and update the log header file
|
|
*
|
|
* Unmap the current log segments.
|
|
* Read the log header. Update the number of records, and number of files
|
|
*
|
|
* @param[in/out] mm mapped log object
|
|
*/
|
|
__clib_export void
|
|
clib_maplog_close (clib_maplog_main_t *mm)
|
|
{
|
|
int i, limit;
|
|
u64 file_size_in_bytes;
|
|
|
|
if (!(mm->flags & CLIB_MAPLOG_FLAG_INIT))
|
|
return;
|
|
|
|
clib_maplog_update_header (mm);
|
|
|
|
file_size_in_bytes =
|
|
mm->file_size_in_records * mm->record_size_in_cachelines *
|
|
CLIB_CACHE_LINE_BYTES;
|
|
|
|
limit = (mm->flags & CLIB_MAPLOG_FLAG_CIRCULAR) ? 1 : 2;
|
|
|
|
/* unmap current + next segments */
|
|
for (i = 0; i < limit; i++)
|
|
{
|
|
(void) munmap ((u8 *) mm->file_baseva[i], file_size_in_bytes);
|
|
vec_free (mm->filenames[i]);
|
|
}
|
|
|
|
vec_free (mm->file_basename);
|
|
vec_free (mm->header_filename);
|
|
clib_memset (mm, 0, sizeof (*mm));
|
|
}
|
|
|
|
/**
|
|
* @brief format a log header
|
|
*
|
|
* Usage: s = format (0, "%U", format_maplog_header, headerp, verbose);
|
|
* @param [in] h clib_maplog_header_t pointer
|
|
* @param [in] verbose self-explanatory
|
|
*/
|
|
__clib_export u8 *
|
|
format_maplog_header (u8 *s, va_list *args)
|
|
{
|
|
clib_maplog_header_t *h = va_arg (*args, clib_maplog_header_t *);
|
|
int verbose = va_arg (*args, int);
|
|
|
|
if (!verbose)
|
|
goto brief;
|
|
s = format (s, "basename %s ", h->file_basename);
|
|
s = format (s, "log ver %d.%d.%d app id %u ver %d.%d.%d %s %s\n",
|
|
h->maplog_major_version,
|
|
h->maplog_minor_version,
|
|
h->maplog_patch_version,
|
|
h->application_id,
|
|
h->application_major_version,
|
|
h->application_minor_version, h->application_patch_version,
|
|
h->maplog_flag_circular ? "circular" : "linear",
|
|
h->maplog_flag_wrapped ? "wrapped" : "not wrapped");
|
|
s = format (s, " records are %d %d-byte cachelines\n",
|
|
h->record_size_in_cachelines, h->cacheline_size);
|
|
s = format (s, " files are %lld records long, %lld files\n",
|
|
h->file_size_in_records, h->number_of_files);
|
|
s = format (s, " %lld records total\n", h->number_of_records);
|
|
return s;
|
|
|
|
brief:
|
|
s = format (s, "%s %lld records %lld files %lld records/file",
|
|
h->file_basename, h->number_of_records, h->number_of_files,
|
|
h->file_size_in_records);
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* @brief Process a complete maplog
|
|
*
|
|
* Reads the maplog header. Map and process all log segments in order.
|
|
* Calls the callback function once per file with a record count.
|
|
*
|
|
* Note: if the file header isn't updated by calling
|
|
* clib_maplog_close(), it will appear to have an infinite
|
|
* number of records in an infinite number of files.
|
|
*
|
|
* So long as the callback function understands that possibility
|
|
* - by simply ignoring NULL records - the scheme still
|
|
* works...
|
|
*
|
|
* @param [in] file_basename Same basename supplied to clib_maplog_init
|
|
* @param [in] fp_arg Callback function pointer
|
|
*/
|
|
__clib_export int
|
|
clib_maplog_process (char *file_basename, void *fp_arg)
|
|
{
|
|
clib_maplog_header_t _h, *h = &_h;
|
|
int fd, rv = 0;
|
|
u64 file_index;
|
|
u64 file_size_in_bytes;
|
|
u8 *header_filename, *this_filename = 0;
|
|
u8 *file_baseva;
|
|
int (*fp) (clib_maplog_header_t *, void *data, u64 count);
|
|
u64 records_this_file, records_left;
|
|
ASSERT (fp_arg);
|
|
|
|
fp = fp_arg;
|
|
|
|
header_filename = format (0, "%s_header%c", file_basename, 0);
|
|
|
|
fd = open ((char *) header_filename, O_RDONLY, 0600);
|
|
if (fd < 0)
|
|
{
|
|
clib_unix_warning ("open maplog header");
|
|
rv = -1;
|
|
goto out;
|
|
}
|
|
rv = read (fd, h, sizeof (*h));
|
|
if (rv != sizeof (*h))
|
|
{
|
|
clib_unix_warning ("read maplog header");
|
|
rv = -2;
|
|
goto out;
|
|
}
|
|
(void) close (fd);
|
|
fd = -1;
|
|
|
|
file_size_in_bytes = h->file_size_in_records
|
|
* h->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES;
|
|
|
|
records_left = h->number_of_records;
|
|
|
|
for (file_index = 0; file_index < h->number_of_files; file_index++)
|
|
{
|
|
vec_reset_length (this_filename);
|
|
this_filename = format (this_filename, "%s_%llu%c", file_basename,
|
|
file_index, 0);
|
|
fd = open ((char *) this_filename, O_RDONLY, 0600);
|
|
if (fd < 0)
|
|
{
|
|
rv = -3;
|
|
goto out;
|
|
}
|
|
|
|
file_baseva =
|
|
mmap (0, file_size_in_bytes, PROT_READ, MAP_SHARED, fd, 0);
|
|
(void) close (fd);
|
|
fd = -1;
|
|
if (file_baseva == (u8 *) MAP_FAILED)
|
|
{
|
|
clib_unix_warning ("mmap");
|
|
rv = -4;
|
|
goto out;
|
|
}
|
|
|
|
records_this_file = (records_left > h->file_size_in_records) ?
|
|
h->file_size_in_records : records_left;
|
|
|
|
/*
|
|
* Normal log, or a circular non-wrapped log, or a circular
|
|
* wrapped log which happens to be exactly linear
|
|
*/
|
|
if (h->maplog_flag_circular == 0 || h->maplog_flag_wrapped == 0 ||
|
|
((h->number_of_records % h->file_size_in_records) == 0))
|
|
(*fp) (h, file_baseva, records_this_file);
|
|
else
|
|
{
|
|
/* "Normal" wrapped circular log */
|
|
u64 first_chunk_record_index = h->number_of_records &
|
|
(h->file_size_in_records - 1);
|
|
u64 first_chunk_number_of_records = records_this_file -
|
|
first_chunk_record_index;
|
|
u8 *chunk_baseva = file_baseva +
|
|
first_chunk_record_index * h->record_size_in_cachelines *
|
|
h->cacheline_size;
|
|
(*fp) (h, chunk_baseva, first_chunk_number_of_records);
|
|
(*fp) (h, file_baseva,
|
|
records_this_file - first_chunk_number_of_records);
|
|
}
|
|
|
|
if (munmap (file_baseva, file_size_in_bytes) < 0)
|
|
{
|
|
clib_warning ("munmap");
|
|
rv = -5;
|
|
/* but don't stop... */
|
|
}
|
|
records_left -= records_this_file;
|
|
if (records_left == 0)
|
|
break;
|
|
}
|
|
|
|
out:
|
|
if (fd >= 0)
|
|
(void) close (fd);
|
|
|
|
vec_free (this_filename);
|
|
vec_free (header_filename);
|
|
return rv;
|
|
}
|
|
|
|
|
|
/*
|
|
* fd.io coding-style-patch-verification: ON
|
|
*
|
|
* Local Variables:
|
|
* eval: (c-set-style "gnu")
|
|
* End:
|
|
*/
|