All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dynatrace.openkit.core.caching.BeaconCacheEntry Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2018-2021 Dynatrace LLC
 *
 * 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.
 */

package com.dynatrace.openkit.core.caching;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Represents an entry in the {@link BeaconCacheImpl}.
 *
 * 

* The caller is responsible to lock this element, before the first method is invoked using * {@link #lock()} and after the last operation is invoked {@link #unlock()} must be called. *

*/ class BeaconCacheEntry { /** * List storing all active event data. */ private LinkedList eventData = new LinkedList<>(); /** * List storing all active session data. */ private LinkedList actionData = new LinkedList<>(); /** * Lock object for locking access to session & event data. */ private final Lock lock = new ReentrantLock(); /** * List storing all event data being sent. */ private LinkedList eventDataBeingSent; /** * List storing all action data being sent. */ private LinkedList actionDataBeingSent; /** * Total number of bytes consumed by this entry. */ private long totalNumBytes = 0; /** * Lock this {@link BeaconCacheEntry} for reading & writing. * *

* When locking is no longer required, {@link #unlock()} must be called. *

*/ void lock() { lock.lock(); } /** * Release this {@link BeaconCacheEntry} lock, so that other threads can access this object. * *

* When calling this method ensure {@link #lock()} was called before. *

*/ void unlock() { lock.unlock(); } /** * Add new event data record to cache. * * @param record The new record to add. */ void addEventData(BeaconCacheRecord record) { eventData.add(record); totalNumBytes += record.getDataSizeInBytes(); } /** * Add new action data record to the cache. * * @param record The new record to add. */ void addActionData(BeaconCacheRecord record) { actionData.add(record); totalNumBytes += record.getDataSizeInBytes(); } /** * Test if data shall be copied, before creating chunks for sending. * * @return {@code true} if data must be copied, {@code false} otherwise. */ boolean needsDataCopyBeforeSending() { return !hasDataToSend(); } /** * Copy data for sending. */ void copyDataForSending() { actionDataBeingSent = actionData; eventDataBeingSent = eventData; actionData = new LinkedList<>(); eventData = new LinkedList<>(); totalNumBytes = 0; // data which is being sent is not counted } /** * Get next data chunk to send to the Dynatrace backend system. * *

* This method is called from beacon sending thread. *

* * @param chunkPrefix The prefix to add to each chunk. * @param maxSize The maximum size in characters for one chunk. * @param delimiter The delimiter between data chunks. * * @return The string to send or an empty string if there is no more data to send. */ String getChunk(String chunkPrefix, int maxSize, char delimiter) { if (!hasDataToSend()) { return ""; } return getNextChunk(chunkPrefix, maxSize, delimiter); } /** * Test if there is more data to send (to chunk). * * @return {@code true} if there is more data, {@code false} otherwise. */ boolean hasDataToSend() { return (eventDataBeingSent != null && !eventDataBeingSent.isEmpty()) || (actionDataBeingSent != null && !actionDataBeingSent.isEmpty()); } /** * Get the next chunk. * * @param chunkPrefix The prefix to add to each chunk. * @param maxSize The maximum size in characters for one chunk. * @param delimiter The delimiter between data chunks. * * @return The string to send or an empty string if there is no more data to send. */ private String getNextChunk(String chunkPrefix, int maxSize, char delimiter) { // create the string builder StringBuilder beaconBuilder = new StringBuilder(maxSize); // append the chunk prefix beaconBuilder.append(chunkPrefix); // append data from both lists // note the order is currently important -> event data goes first, then action data chunkifyDataList(beaconBuilder, eventDataBeingSent, maxSize, delimiter); chunkifyDataList(beaconBuilder, actionDataBeingSent, maxSize, delimiter); return beaconBuilder.toString(); } private static void chunkifyDataList(StringBuilder chunkBuilder, LinkedList dataBeingSent, int maxSize, char delimiter) { Iterator iterator = dataBeingSent.iterator(); while (iterator.hasNext() && chunkBuilder.length() <= maxSize) { // mark the record for sending BeaconCacheRecord record = iterator.next(); record.markForSending(); // append delimiter & data chunkBuilder.append(delimiter).append(record.getData()); } } /** * Remove data that was previously marked for sending when {@link #getNextChunk(String, int, char)} was called. */ void removeDataMarkedForSending() { if (!hasDataToSend()) { // data has not been copied yet - avoid NPE return; } Iterator iterator = eventDataBeingSent.iterator(); while (iterator.hasNext() && iterator.next().isMarkedForSending()) { iterator.remove(); } if (!iterator.hasNext()) { // only check action data, if all event data has been traversed, otherwise it's just waste of cpu time iterator = actionDataBeingSent.iterator(); while (iterator.hasNext() && iterator.next().isMarkedForSending()) { iterator.remove(); } } } /** * This method removes the marked for sending and prepends the copied data back to the data. */ void resetDataMarkedForSending() { if (!hasDataToSend()) { // data has not been copied yet - avoid NPE return; } // reset the "sending marks" and in the same traversal count the bytes which are added back long numBytes = 0; for (BeaconCacheRecord record : eventDataBeingSent) { record.unsetSending(); numBytes += record.getDataSizeInBytes(); } for (BeaconCacheRecord record : actionDataBeingSent) { record.unsetSending(); numBytes += record.getDataSizeInBytes(); } // merge data eventDataBeingSent.addAll(eventData); actionDataBeingSent.addAll(actionData); eventData = eventDataBeingSent; actionData = actionDataBeingSent; eventDataBeingSent = null; actionDataBeingSent = null; totalNumBytes += numBytes; } /** * Get total number of bytes used. * *

* Note: The number of bytes is calculated from the lists where active records are added. * Data that is currently being sent is not taken into account, since we assume sending is * successful and therefore this data is just temporarily stored. *

* * @return Sum of data size in bytes for each {@link BeaconCacheRecord}. */ long getTotalNumberOfBytes() { return totalNumBytes; } /** * Remove all {@link BeaconCacheRecord beacon cache records} from event and action data * which are older than given minTimestamp * *

* Records which are currently being sent are not evicted. *

* * @param minTimestamp The minimum timestamp allowed. * * @return The total number of removed records. */ int removeRecordsOlderThan(long minTimestamp) { int numRecordsRemoved = removeRecordsOlderThan(eventData, minTimestamp); numRecordsRemoved += removeRecordsOlderThan(actionData, minTimestamp); return numRecordsRemoved; } /** * Remove all {@link BeaconCacheRecord beacon cache records} from {@code records}. * * @param minTimestamp The minimum timestamp allowed. * * @return The number of records removed from {@code records}. */ private static int removeRecordsOlderThan(List records, long minTimestamp) { int numRecordsRemoved = 0; Iterator iterator = records.iterator(); while (iterator.hasNext()) { BeaconCacheRecord record = iterator.next(); if (record.getTimestamp() < minTimestamp) { iterator.remove(); numRecordsRemoved++; } } return numRecordsRemoved; } /** * Remove up to {@code numRecords} records from event & action data, compared by their age. * *

* Note not all event/action data entries are traversed, only the first action data & first event * data is removed and compared against each other, which one to remove first. If the first action's timestamp and * first event's timestamp are equal, the first event is removed. *

* * @param numRecords The number of records. * * @return Number of actually removed records. */ int removeOldestRecords(int numRecords) { int numRecordsRemoved = 0; Iterator eventsIterator = eventData.iterator(); Iterator actionsIterator = actionData.iterator(); BeaconCacheRecord currentEvent = eventsIterator.hasNext() ? eventsIterator.next() : null; BeaconCacheRecord currentAction = actionsIterator.hasNext() ? actionsIterator.next() : null; while (numRecordsRemoved < numRecords && (currentEvent != null || currentAction != null)) { if (currentEvent == null) { // actions is not null -> remove action currentAction = removeAndAdvanceIterator(actionsIterator); } else if (currentAction == null) { // events is not null -> remove event currentEvent = removeAndAdvanceIterator(eventsIterator); } else { // both are not null -> compare by timestamp and take the older one if (currentAction.getTimestamp() < currentEvent.getTimestamp()) { // first action is older than first event currentAction = removeAndAdvanceIterator(actionsIterator); } else { // first event is older than first action currentEvent = removeAndAdvanceIterator(eventsIterator); } } numRecordsRemoved++; } return numRecordsRemoved; } private static E removeAndAdvanceIterator(Iterator iterator) { iterator.remove(); return iterator.hasNext() ? iterator.next() : null; } /** * Get a shallow copy of event data. * *

* This method shall only be used for testing purposes. *

*/ List getEventData() { return new LinkedList<>(eventData); } /** * Get a snapshot of action data. * *

* This method shall only be used for testing purposes. *

*/ List getActionData() { return new LinkedList<>(actionData); } /** * Get a readonly list of event data being sent. * *

* This method shall only be used for testing purposes. *

*/ List getEventDataBeingSent() { return eventDataBeingSent == null ? null : Collections.unmodifiableList(eventDataBeingSent); } /** * Get a readonly list of action data being sent. * *

* This method shall only be used for testing purposes. *

*/ List getActionDataBeingSent() { return actionDataBeingSent == null ? null : Collections.unmodifiableList(actionDataBeingSent); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy