blob: 60db1cd9a3e5dffdee8a7d567f696c685a7d6222 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* Copyright (c) 2015-2017 Nest Labs, Inc.
* All rights reserved.
*
* 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.
*/
/**
* @file
*
* @brief
* Management of the CHIP Event Logging.
*
*/
#pragma once
#include "EventLoggingDelegate.h"
#include "EventLoggingTypes.h"
#include <access/SubjectDescriptor.h>
#include <app/MessageDef/EventDataIB.h>
#include <app/MessageDef/StatusIB.h>
#include <app/ObjectList.h>
#include <app/util/basic-types.h>
#include <lib/core/CHIPCircularTLVBuffer.h>
#include <lib/support/PersistedCounter.h>
#include <messaging/ExchangeMgr.h>
#include <system/SystemMutex.h>
#define CHIP_CONFIG_EVENT_GLOBAL_PRIORITY PriorityLevel::Debug
namespace chip {
namespace app {
constexpr const uint32_t kEventManagementProfile = 0x1;
constexpr const uint32_t kFabricIndexTag = 0x1;
constexpr size_t kMaxEventSizeReserve = 512;
constexpr uint16_t kRequiredEventField =
(1 << to_underlying(EventDataIB::Tag::kPriority)) | (1 << to_underlying(EventDataIB::Tag::kPath));
/**
* @brief
* Internal event buffer, built around the TLV::CHIPCircularTLVBuffer
*/
class CircularEventBuffer : public TLV::CHIPCircularTLVBuffer
{
public:
/**
* @brief
* A constructor for the CircularEventBuffer (internal API).
*/
CircularEventBuffer() : CHIPCircularTLVBuffer(nullptr, 0){};
/**
* @brief
* A Init for the CircularEventBuffer (internal API).
*
* @param[in] apBuffer The actual storage to use for event storage.
*
* @param[in] aBufferLength The length of the \c apBuffer in bytes.
*
* @param[in] apPrev The pointer to CircularEventBuffer storing
* events of lesser priority.
*
* @param[in] apNext The pointer to CircularEventBuffer storing
* events of greater priority.
*
* @param[in] aPriorityLevel CircularEventBuffer priority level
*/
void Init(uint8_t * apBuffer, uint32_t aBufferLength, CircularEventBuffer * apPrev, CircularEventBuffer * apNext,
PriorityLevel aPriorityLevel);
/**
* @brief
* A helper function that determines whether the event of
* specified priority is final destination
*
* @param[in] aPriority Priority of the event.
*
* @retval true/false event's priority is same as current buffer's priority, otherwise, false
*/
bool IsFinalDestinationForPriority(PriorityLevel aPriority) const;
PriorityLevel GetPriority() { return mPriority; }
CircularEventBuffer * GetPreviousCircularEventBuffer() { return mpPrev; }
CircularEventBuffer * GetNextCircularEventBuffer() { return mpNext; }
void SetRequiredSpaceforEvicted(size_t aRequiredSpace) { mRequiredSpaceForEvicted = aRequiredSpace; }
size_t GetRequiredSpaceforEvicted() const { return mRequiredSpaceForEvicted; }
~CircularEventBuffer() override = default;
private:
CircularEventBuffer * mpPrev = nullptr; ///< A pointer CircularEventBuffer storing events less important events
CircularEventBuffer * mpNext = nullptr; ///< A pointer CircularEventBuffer storing events more important events
PriorityLevel mPriority = PriorityLevel::Invalid; ///< The buffer is the final bucket for events of this priority. Events of
///< lesser priority are dropped when they get bumped out of this buffer
// The counter we're going to actually use.
MonotonicallyIncreasingCounter * mpEventNumberCounter = nullptr;
// The backup counter to use if no counter is provided for us.
MonotonicallyIncreasingCounter mNonPersistedCounter;
size_t mRequiredSpaceForEvicted = 0; ///< Required space for previous buffer to evict event to new buffer
};
class CircularEventReader;
/**
* @brief
* A CircularEventBufferWrapper which has a pointer to the "current CircularEventBuffer". When trying to locate next buffer,
* if nothing left there update its CircularEventBuffer until the buffer with data has been found,
* the tlv reader will have a pointer to this impl.
*/
class CircularEventBufferWrapper : public TLV::CHIPCircularTLVBuffer
{
public:
CircularEventBufferWrapper() : CHIPCircularTLVBuffer(nullptr, 0), mpCurrent(nullptr){};
CircularEventBuffer * mpCurrent;
private:
CHIP_ERROR GetNextBuffer(chip::TLV::TLVReader & aReader, const uint8_t *& aBufStart, uint32_t & aBufLen) override;
};
enum class EventManagementStates
{
Idle = 1, // No log offload in progress, log offload can begin without any constraints
InProgress = 2, // Log offload in progress
Shutdown = 3 // Not capable of performing any logging operation
};
/**
* @brief
* A helper class used in initializing logging management.
*
* The class is used to encapsulate the resources allocated by the caller and denotes
* resources to be used in logging events of a particular priority. Note that
* while resources referring to the counters are used exclusively by the
* particular priority level, the buffers are shared between `this` priority
* level and events that are "more" important.
*/
struct LogStorageResources
{
// TODO: Update CHIPCircularTLVBuffer with size_t for buffer size, then use ByteSpan
uint8_t * mpBuffer =
nullptr; // Buffer to be used as a storage at the particular priority level and shared with more important events.
// Must not be nullptr. Must be large enough to accommodate the largest event emitted by the system.
uint32_t mBufferSize = 0; ///< The size, in bytes, of the `mBuffer`.
PriorityLevel mPriority =
PriorityLevel::Invalid; // Log priority level associated with the resources provided in this structure.
};
/**
* @brief
* A class for managing the in memory event logs.
* Assume we have two importance levels. DEBUG and CRITICAL, for simplicity,
* A new incoming event will always get logged in buffer with debug buffer no matter it is Debug or CRITICAL event.
* In order for it to get logged, we need to free up space. So we look at the tail of the buffer.
* If the tail of the buffer contains a DEBUG event, that will be dropped. If the tail of the buffer contains
* a CRITICAL event, that event will be 'promoted' to the next level of buffers, where it will undergo the same procedure
* The outcome of this management policy is that the critical buffer is dedicated to the critical events, and the
* debug buffer is shared between critical and debug events.
*/
class EventManagement
{
public:
/**
* @brief
* Initialize the EventManagement with an array of LogStorageResources. The
* array must provide a resource for each valid priority level, the elements
* of the array must be in increasing numerical value of priority (and in
* increasing priority); the first element in the array corresponds to the
* resources allocated for least important events, and the last element
* corresponds to the most critical events.
*
* @param[in] apExchangeManager ExchangeManager to be used with this logging subsystem
*
* @param[in] aNumBuffers Number of elements in inLogStorageResources array
*
* @param[in] apCircularEventBuffer An array of CircularEventBuffer for each priority level.
*
* @param[in] apLogStorageResources An array of LogStorageResources for each priority level.
*
*/
void Init(Messaging::ExchangeManager * apExchangeManager, uint32_t aNumBuffers, CircularEventBuffer * apCircularEventBuffer,
const LogStorageResources * const apLogStorageResources, Platform::PersistedStorage::Key * apCounterKey,
uint32_t aCounterEpoch, PersistedCounter * apPersistedCounter);
void InitializeCounter(Platform::PersistedStorage::Key * apCounterKey, uint32_t aCounterEpoch,
PersistedCounter * apPersistedCounter);
static EventManagement & GetInstance();
/**
* @brief Create EventManagement object and initialize the logging management
* subsystem with provided resources.
*
* Initialize the EventManagement with an array of LogStorageResources. The
* array must provide a resource for each valid priority level, the elements
* of the array must be in increasing numerical value of priority (and in
* decreasing priority); the first element in the array corresponds to the
* resources allocated for the most critical events, and the last element
* corresponds to the least important events.
*
* @param[in] apExchangeManager ExchangeManager to be used with this logging subsystem
*
* @param[in] aNumBuffers Number of elements in inLogStorageResources array
*
* @param[in] apCircularEventBuffer An array of CircularEventBuffer for each priority level.
* @param[in] apLogStorageResources An array of LogStorageResources for each priority level.
*
* @note This function must be called prior to the logging being used.
*/
static void CreateEventManagement(Messaging::ExchangeManager * apExchangeManager, uint32_t aNumBuffers,
CircularEventBuffer * apCircularEventBuffer,
const LogStorageResources * const apLogStorageResources,
Platform::PersistedStorage::Key * apCounterKey, uint32_t aCounterEpoch,
PersistedCounter * apPersistedCounter);
static void DestroyEventManagement();
#if !CHIP_SYSTEM_CONFIG_NO_LOCKING
class ScopedLock
{
public:
ScopedLock(EventManagement & aEventManagement) : mEventManagement(aEventManagement) { mEventManagement.mAccessLock.Lock(); }
~ScopedLock() { mEventManagement.mAccessLock.Unlock(); }
private:
EventManagement & mEventManagement;
};
#endif // !CHIP_SYSTEM_CONFIG_NO_LOCKING
/**
* @brief
* Log an event via a EventLoggingDelegate, with options.
*
* The EventLoggingDelegate writes the event metadata and calls the `apDelegate`
* with an TLV::TLVWriter reference so that the user code can emit
* the event data directly into the event log. This form of event
* logging minimizes memory consumption, as event data is serialized
* directly into the target buffer. The event data MUST contain
* context tags to be interpreted within the schema identified by
* `ClusterID` and `EventId`. The tag of the first element will be
* ignored; the event logging system will replace it with the
* eventData tag.
*
* The event is logged if the schema priority exceeds the logging
* threshold specified in the LoggingConfiguration. If the event's
* priority does not meet the current threshold, it is dropped and
* the function returns a `0` as the resulting event ID.
*
* This variant of the invocation permits the caller to set any
* combination of `EventOptions`:
* - timestamp, when 0 defaults to the current time at the point of
* the call,
* - "root" section of the event source (event source and cluster ID);
* if NULL, it defaults to the current device. the event is marked as
* relating to the device that is making the call,
*
* @param[in] apDelegate The EventLoggingDelegate to serialize the event data
*
* @param[in] aEventOptions The options for the event metadata.
*
* @param[out] aEventNumber The event Number if the event was written to the
* log, 0 otherwise.
*
* @return CHIP_ERROR CHIP Error Code
*/
CHIP_ERROR LogEvent(EventLoggingDelegate * apDelegate, const EventOptions & aEventOptions, EventNumber & aEventNumber);
/**
* @brief
* A helper method to get tlv reader along with buffer has data from particular priority
*
* @param[in,out] aReader A reference to the reader that will be
* initialized with the backing storage from
* the event log
*
* @param[in] aPriority The starting priority for the reader.
* Note that in this case the starting
* priority is somewhat counter intuitive:
* more important events share the buffers
* with less priority events, in addition to
* their dedicated buffers. As a result, the
* reader will traverse the least data when
* the Debug priority is passed in.
*
* @param[in] apBufWrapper CircularEventBufferWrapper
* @return #CHIP_NO_ERROR Unconditionally.
*/
CHIP_ERROR GetEventReader(chip::TLV::TLVReader & aReader, PriorityLevel aPriority,
app::CircularEventBufferWrapper * apBufWrapper);
/**
* @brief
* A function to retrieve events of specified priority since a specified event ID.
*
* Given a TLV::TLVWriter, an priority type, and an event ID, the
* function will fetch events since the
* specified event number. The function will continue fetching events until
* it runs out of space in the TLV::TLVWriter or in the log. The function
* will terminate the event writing on event boundary. The function would filter out event based upon interested path
* specified by read/subscribe request.
*
* @param[in] aWriter The writer to use for event storage
* @param[in] apEventPathList the interested EventPathParams list
*
* @param[in,out] aEventMin On input, the Event number is the one we're fetching. On
* completion, the event number of the next one we plan to fetch.
*
* @param[out] aEventCount The number of fetched event
* @param[in] aSubjectDescriptor Subject descriptor for current read handler
* @retval #CHIP_END_OF_TLV The function has reached the end of the
* available log entries at the specified
* priority level
*
* @retval #CHIP_ERROR_NO_MEMORY The function ran out of space in the
* aWriter, more events in the log are
* available.
*
* @retval #CHIP_ERROR_BUFFER_TOO_SMALL The function ran out of space in the
* aWriter, more events in the log are
* available.
*
*/
CHIP_ERROR FetchEventsSince(chip::TLV::TLVWriter & aWriter, const ObjectList<EventPathParams> * apEventPathList,
EventNumber & aEventMin, size_t & aEventCount,
const Access::SubjectDescriptor & aSubjectDescriptor);
/**
* @brief
* Fetch the most recently vended Number for a particular priority level
*
* @return EventNumber most recently vended event Number for that event priority
*/
EventNumber GetLastEventNumber() const { return mLastEventNumber; }
/**
* @brief
* IsValid returns whether the EventManagement instance is valid
*/
bool IsValid(void) { return EventManagementStates::Shutdown != mState; };
/**
* Logger would save last logged event number and initial written event bytes number into schedule event number array
*/
void SetScheduledEventInfo(EventNumber & aEventNumber, uint32_t & aInitialWrittenEventBytes) const;
private:
/**
* @brief
* Internal structure for traversing events.
*/
struct EventEnvelopeContext
{
EventEnvelopeContext() {}
int mFieldsToRead = 0;
/* PriorityLevel and DeltaTime are there if that is not first event when putting events in report*/
#if CHIP_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS & CHIP_SYSTEM_CONFIG_PLATFORM_PROVIDES_TIME
Timestamp mCurrentTime = Timestamp::System(System::Clock::kZero);
#else
Timestamp mCurrentTime = Timestamp::Epoch(System::Clock::kZero);
#endif
PriorityLevel mPriority = PriorityLevel::First;
ClusterId mClusterId = 0;
EndpointId mEndpointId = 0;
EventId mEventId = 0;
EventNumber mEventNumber = 0;
FabricIndex mFabricIndex = kUndefinedFabricIndex;
};
void VendEventNumber();
CHIP_ERROR CalculateEventSize(EventLoggingDelegate * apDelegate, const EventOptions * apOptions, uint32_t & requiredSize);
/**
* @brief Helper function for writing event header and data according to event
* logging protocol.
*
* @param[in,out] apContext EventLoadOutContext, initialized with stateful
* information for the buffer. State is updated
* and preserved by ConstructEvent using this context.
*
* @param[in] apDelegate The EventLoggingDelegate to serialize the event data
*
* @param[in] apOptions EventOptions describing timestamp and other tags
* relevant to this event.
*
*/
CHIP_ERROR ConstructEvent(EventLoadOutContext * apContext, EventLoggingDelegate * apDelegate, const EventOptions * apOptions);
// Internal function to log event
CHIP_ERROR LogEventPrivate(EventLoggingDelegate * apDelegate, const EventOptions & aEventOptions, EventNumber & aEventNumber);
/**
* @brief copy the event outright to next buffer with higher priority
*
* @param[in] apEventBuffer CircularEventBuffer
*
*/
CHIP_ERROR CopyToNextBuffer(CircularEventBuffer * apEventBuffer);
/**
* @brief eusure current buffer has enough space, if not, when current buffer is final destination of last tail's event
* priority, we need to drop event, otherwises, move the last event to the buffer with higher priority
*
* @param[in] aRequiredSpace require space
*
*/
CHIP_ERROR EnsureSpaceInCircularBuffer(size_t aRequiredSpace);
/**
* @brief
* Internal API used to implement #FetchEventsSince
*
* Iterator function to used to copy an event from the log into a
* TLVWriter. The included apContext contains the context of the copy
* operation, including the TLVWriter that will hold the copy of an
* event. If event cannot be written as a whole, the TLVWriter will
* be rolled back to event boundary.
*
* @retval #CHIP_END_OF_TLV Function reached the end of the event
* @retval #CHIP_ERROR_NO_MEMORY Function could not write a portion of
* the event to the TLVWriter.
* @retval #CHIP_ERROR_BUFFER_TOO_SMALL Function could not write a
* portion of the event to the TLVWriter.
*/
static CHIP_ERROR CopyEventsSince(const TLV::TLVReader & aReader, size_t aDepth, void * apContext);
/**
* @brief Internal iterator function used to scan and filter though event logs
*
* The function is used to scan through the event log to find events matching the spec in the supplied context.
* Particularly, it would check against mStartingEventNumber, and skip fetched event.
*/
static CHIP_ERROR EventIterator(const TLV::TLVReader & aReader, size_t aDepth, EventLoadOutContext * apEventLoadOutContext,
EventEnvelopeContext * event);
/**
* @brief Internal iterator function used to fetch event into EventEnvelopeContext, then EventIterator would filter event
* based upon EventEnvelopeContext
*
*/
static CHIP_ERROR FetchEventParameters(const TLV::TLVReader & aReader, size_t aDepth, void * apContext);
/**
* @brief Internal iterator function used to scan and filter though event logs
* First event gets a timestamp, subsequent ones get a delta T
* First event in the sequence gets a event number neatly packaged
*/
static CHIP_ERROR CopyAndAdjustDeltaTime(const TLV::TLVReader & aReader, size_t aDepth, void * apContext);
/**
* @brief checking if the tail's event can be moved to higher priority, if not, dropped, if yes, note how much space it
* requires, and return.
*/
static CHIP_ERROR EvictEvent(chip::TLV::CHIPCircularTLVBuffer & aBuffer, void * apAppData, TLV::TLVReader & aReader);
static CHIP_ERROR AlwaysFail(chip::TLV::CHIPCircularTLVBuffer & aBuffer, void * apAppData, TLV::TLVReader & aReader)
{
return CHIP_ERROR_NO_MEMORY;
};
/**
* @brief Check whether the event instance represented by the EventEnvelopeContext should be included in the report.
*
* @retval CHIP_ERROR_UNEXPECTED_EVENT This path should be excluded in the generated event report.
* @retval CHIP_EVENT_ID_FOUND This path should be included in the generated event report.
* @retval CHIP_ERROR_ACCESS_DENIED This path should be included in the generated event report, but the client does not have
* . enough privilege to access it.
*
* TODO: Consider using CHIP_NO_ERROR, CHIP_ERROR_SKIP_EVENT, CHIP_ERROR_ACCESS_DENINED or some enum to represent the checking
* result.
*/
static CHIP_ERROR CheckEventContext(EventLoadOutContext * eventLoadOutContext, const EventEnvelopeContext & event);
/**
* @brief copy event from circular buffer to target buffer for report
*/
static CHIP_ERROR CopyEvent(const TLV::TLVReader & aReader, TLV::TLVWriter & aWriter, EventLoadOutContext * apContext);
/**
* @brief construct EventStatusIB to target buffer for report
*/
static CHIP_ERROR WriteEventStatusIB(TLV::TLVWriter & aWriter, const ConcreteEventPath & aEvent, StatusIB aStatus);
/**
* @brief
* A function to get the circular buffer for particular priority
*
* @param aPriority PriorityLevel
*
* @return A pointer for the CircularEventBuffer
*/
CircularEventBuffer * GetPriorityBuffer(PriorityLevel aPriority) const;
// EventBuffer for debug level,
CircularEventBuffer * mpEventBuffer = nullptr;
Messaging::ExchangeManager * mpExchangeMgr = nullptr;
EventManagementStates mState = EventManagementStates::Shutdown;
uint32_t mBytesWritten = 0;
#if !CHIP_SYSTEM_CONFIG_NO_LOCKING
System::Mutex mAccessLock;
#endif // !CHIP_SYSTEM_CONFIG_NO_LOCKING
// The counter we're going to actually use.
MonotonicallyIncreasingCounter * mpEventNumberCounter = nullptr;
// The backup counter to use if no counter is provided for us.
MonotonicallyIncreasingCounter mNonPersistedCounter;
EventNumber mLastEventNumber = 0; ///< Last event Number vended for this priority
Timestamp mLastEventTimestamp; ///< The timestamp of the last event in this buffer
};
} // namespace app
} // namespace chip