001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: FileSummaryLN.java,v 1.22.2.5 2008/01/07 15:14:16 cwl Exp $
007: */
008:
009: package com.sleepycat.je.tree;
010:
011: import java.io.UnsupportedEncodingException;
012: import java.nio.ByteBuffer;
013:
014: import com.sleepycat.je.DatabaseException;
015: import com.sleepycat.je.cleaner.FileSummary;
016: import com.sleepycat.je.cleaner.PackedOffsets;
017: import com.sleepycat.je.cleaner.TrackedFileSummary;
018: import com.sleepycat.je.dbi.DatabaseImpl;
019: import com.sleepycat.je.dbi.MemoryBudget;
020: import com.sleepycat.je.log.LogEntryType;
021: import com.sleepycat.je.log.LogException;
022: import com.sleepycat.je.log.LogUtils;
023:
024: /**
025: * A FileSummaryLN represents a Leaf Node in the UtilizationProfile database.
026: *
027: * <p>The contents of the FileSummaryLN are not fixed until the moment at which
028: * the LN is added to the log. A base summary object contains the summary last
029: * added to the log. A tracked summary object contains live summary info being
030: * updated in real time. The tracked summary is added to the base summary just
031: * before logging it, and then the tracked summary is reset. This ensures that
032: * the logged summary will accurately reflect the totals calculated at the
033: * point in the log where the LN is added.</p>
034: *
035: * <p>This is all done in the writeToLog method, which operates under the log
036: * write latch. All utilization tracking must be done under the log write
037: * latch.</p>
038: *
039: * <p>In record version 1, obsolete offset tracking was added and multiple
040: * records are stored for a single file rather than a single record. Each
041: * record contains the offsets that were tracked since the last record was
042: * written.
043: *
044: * <p>The key is 8 bytes: 4 bytes for the file number followed by 4 bytes for
045: * the sequence number. The lowest valued key for a given file contains the
046: * most recent summary information, while to get a complete list of obsolete
047: * offsets all records for the file must be read. A range search using just
048: * the first 4 bytes can be used to find the most recent record -- this is
049: * possible because the sequence number values are decreasing over time for a
050: * given file. Here are example keys for three summary records in file 1:</p>
051: *
052: * <pre>
053: * (file=1, sequence=Integer.MAX_VALUE - 300)
054: * (file=1, sequence=Integer.MAX_VALUE - 200)
055: * (file=1, sequence=Integer.MAX_VALUE - 100)
056: * </pre>
057: *
058: * <p>The sequence number is the number of obsolete entries counted so far,
059: * subtracted from Integer.MAX_VALUE to cause the latest written record to have
060: * the lowest key.</p>
061: *
062: * <h3>Log version information</h3>
063: * <p>Version 0: Keys are old format strings. No obsolete detail is
064: * present.</p>
065: * <p>Version 1: Keys are two 4 byte integers: {file, sequence}. Obsolete
066: * detail is present. Some offsets may be invalid if RMW was used.</p>
067: * <p>Version 2: The RMW problem with invalid offsets was corrected. There is
068: * no data format change; all versions of JE 2.0.x can read version 1.</p>
069: *
070: * @see com.sleepycat.je.cleaner.UtilizationProfile
071: */
072: public final class FileSummaryLN extends LN {
073:
074: private static final String BEGIN_TAG = "<fileSummaryLN>";
075: private static final String END_TAG = "</fileSummaryLN>";
076:
077: private FileSummary baseSummary;
078: private TrackedFileSummary trackedSummary;
079: private PackedOffsets obsoleteOffsets;
080: private boolean needOffsets;
081: private byte logVersion;
082:
083: /**
084: * Creates a new LN with a given base summary.
085: */
086: public FileSummaryLN(FileSummary baseSummary) {
087: super (new byte[0]);
088: assert baseSummary != null;
089: this .baseSummary = baseSummary;
090: obsoleteOffsets = new PackedOffsets();
091: logVersion = -1;
092: }
093:
094: /**
095: * Creates an empty LN to be filled in from the log.
096: */
097: public FileSummaryLN() throws DatabaseException {
098: baseSummary = new FileSummary();
099: obsoleteOffsets = new PackedOffsets();
100: }
101:
102: /**
103: * Sets the live summary object that will be added to the base summary at
104: * the time the LN is logged.
105: */
106: public void setTrackedSummary(TrackedFileSummary trackedSummary) {
107: this .trackedSummary = trackedSummary;
108: needOffsets = true;
109: }
110:
111: /**
112: * Returns the tracked summary, or null if setTrackedSummary was not
113: * called.
114: */
115: public TrackedFileSummary getTrackedSummary() {
116: return trackedSummary;
117: }
118:
119: /**
120: * Returns the base summary for the file that is stored in the LN.
121: */
122: public FileSummary getBaseSummary() {
123: return baseSummary;
124: }
125:
126: /**
127: * Returns the obsolete offsets for the file.
128: */
129: public PackedOffsets getObsoleteOffsets() {
130: return obsoleteOffsets;
131: }
132:
133: /**
134: * Returns true if the given key for this LN is a String file number key.
135: * For the old version of the LN there will be a single record per file.
136: *
137: * If this is a version 0 log entry, the key is a string. However, such an
138: * LN may be migrated by the cleaner, in which case the version will be 1
139: * or greater [#13061]. In the latter case, we can distinguish a string
140: * key by:
141: *
142: * 1) If the key is not 8 bytes long, it has to be a string key.
143: *
144: * 2) If the key is 8 bytes long, but bytes[4] is ascii "0" to "9", then it
145: * must be a string key. bytes[4] to bytes[7] are a sequence number that
146: * is the number of log entries counted. For this number to be greater
147: * than 0x30000000, the binary value of 4 digits starting with ascii "0",
148: * over 400 million log entries would have to occur in a single file; this
149: * should never happen.
150: *
151: * Note that having to rely on method (2) is unlikely. A string key will
152: * only be 8 bytes if the file number reach 8 decimal digits (10,000,000 to
153: * 99,999,999). This is a very large file number and unlikely to have
154: * occurred using JE 1.7.1 or earlier.
155: *
156: * In summary, the only time the algorithm here could fail is if there were
157: * more than 400 million log entries per file, and more than 10 million
158: * were written with JE 1.7.1 or earlier.
159: */
160: public boolean hasStringKey(byte[] bytes) {
161:
162: if (logVersion == 0 || bytes.length != 8) {
163: return true;
164: } else {
165: return (bytes[4] >= '0' && bytes[4] <= '9');
166: }
167: }
168:
169: /**
170: * Convert a FileSummaryLN key from a byte array to a long. The file
171: * number is the first 4 bytes of the key.
172: */
173: public long getFileNumber(byte[] bytes) {
174:
175: if (hasStringKey(bytes)) {
176: try {
177: return Long.valueOf(new String(bytes, "UTF-8"))
178: .longValue();
179: } catch (UnsupportedEncodingException shouldNeverHappen) {
180: assert false : shouldNeverHappen;
181: return 0;
182: }
183: } else {
184: ByteBuffer buf = ByteBuffer.wrap(bytes);
185: return LogUtils.readIntMSB(buf) & 0xFFFFFFFFL;
186: }
187: }
188:
189: /**
190: * Returns the first 4 bytes of the key for the given file number. This
191: * can be used to do a range search to find the first LN for the file.
192: */
193: public static byte[] makePartialKey(long fileNum) {
194:
195: byte[] bytes = new byte[4];
196: ByteBuffer buf = ByteBuffer.wrap(bytes);
197:
198: LogUtils.writeIntMSB(buf, (int) fileNum);
199:
200: return bytes;
201: }
202:
203: /**
204: * Returns the full two-part key for a given file number and unique
205: * sequence. This can be used to insert a new LN.
206: *
207: * @param sequence is a unique identifier for the LN for the given file,
208: * and must be greater than the last sequence.
209: */
210: public static byte[] makeFullKey(long fileNum, int sequence) {
211:
212: assert sequence >= 0;
213:
214: byte[] bytes = new byte[8];
215: ByteBuffer buf = ByteBuffer.wrap(bytes);
216:
217: /*
218: * The sequence is subtracted from MAX_VALUE so that increasing values
219: * will be sorted first. This allows a simple range search to find the
220: * most recent value.
221: */
222: LogUtils.writeIntMSB(buf, (int) fileNum);
223: LogUtils.writeIntMSB(buf, Integer.MAX_VALUE - sequence);
224:
225: return bytes;
226: }
227:
228: /**
229: * Initialize a node that has been faulted in from the log. If this FSLN
230: * contains version 1 offsets that can be incorrect when RMW was used, and
231: * if je.cleaner.rmwFix is enabled, discard the offsets. [#13158]
232: */
233: public void postFetchInit(DatabaseImpl db, long sourceLsn)
234: throws DatabaseException {
235:
236: super .postFetchInit(db, sourceLsn);
237:
238: if (logVersion == 1
239: && db.getDbEnvironment().getUtilizationProfile()
240: .isRMWFixEnabled()) {
241: obsoleteOffsets = new PackedOffsets();
242: }
243: }
244:
245: /*
246: * Dumping
247: */
248:
249: public String toString() {
250: return dumpString(0, true);
251: }
252:
253: public String beginTag() {
254: return BEGIN_TAG;
255: }
256:
257: public String endTag() {
258: return END_TAG;
259: }
260:
261: public String dumpString(int nSpaces, boolean dumpTags) {
262: StringBuffer sb = new StringBuffer();
263: sb.append(super .dumpString(nSpaces, dumpTags));
264: sb.append('\n');
265: if (!isDeleted()) {
266: sb.append(baseSummary.toString());
267: sb.append(obsoleteOffsets.toString());
268: }
269: return sb.toString();
270: }
271:
272: /**
273: * Dump additional fields. Done this way so the additional info can
274: * be within the XML tags defining the dumped log entry.
275: */
276: protected void dumpLogAdditional(StringBuffer sb, boolean verbose) {
277: if (!isDeleted()) {
278: baseSummary.dumpLog(sb, true);
279: if (verbose) {
280: obsoleteOffsets.dumpLog(sb, true);
281: }
282: }
283: }
284:
285: /*
286: * Logging
287: */
288:
289: /**
290: * Log type for transactional entries.
291: */
292: protected LogEntryType getTransactionalLogType() {
293: assert false : "Txnl access to UP db not allowed";
294: return LogEntryType.LOG_FILESUMMARYLN;
295: }
296:
297: /**
298: * @see Node#getLogType
299: */
300: public LogEntryType getLogType() {
301: return LogEntryType.LOG_FILESUMMARYLN;
302: }
303:
304: /**
305: * @see LN#getLogSize
306: */
307: public int getLogSize() {
308: int size = super .getLogSize();
309: if (!isDeleted()) {
310: size += baseSummary.getLogSize();
311: getOffsets();
312: size += obsoleteOffsets.getLogSize();
313: }
314: return size;
315: }
316:
317: /**
318: * @see LN#writeToLog
319: */
320: public void writeToLog(ByteBuffer logBuffer) {
321:
322: /*
323: * Add the tracked (live) summary to the base summary before writing it
324: * to the log, and reset the tracked summary. Do this even when
325: * deleting the LN, so that the tracked summary is cleared.
326: */
327: if (trackedSummary != null) {
328:
329: baseSummary.add(trackedSummary);
330:
331: if (!isDeleted()) {
332: getOffsets();
333: }
334:
335: /* Reset the totals to zero and clear the tracked offsets. */
336: trackedSummary.reset();
337: }
338:
339: super .writeToLog(logBuffer);
340:
341: if (!isDeleted()) {
342: baseSummary.writeToLog(logBuffer);
343: obsoleteOffsets.writeToLog(logBuffer);
344: }
345: }
346:
347: /**
348: * @see LN#readFromLog
349: */
350: public void readFromLog(ByteBuffer itemBuffer, byte entryTypeVersion)
351: throws LogException {
352:
353: super .readFromLog(itemBuffer, entryTypeVersion);
354:
355: logVersion = entryTypeVersion;
356:
357: if (!isDeleted()) {
358: baseSummary.readFromLog(itemBuffer, entryTypeVersion);
359: if (entryTypeVersion > 0) {
360: obsoleteOffsets.readFromLog(itemBuffer,
361: entryTypeVersion);
362: }
363: }
364: }
365:
366: /**
367: * If tracked offsets may be present, get them so they are ready to be
368: * written to the log.
369: */
370: private void getOffsets() {
371: if (needOffsets) {
372: long[] offsets = trackedSummary.getObsoleteOffsets();
373: if (offsets != null) {
374: obsoleteOffsets.pack(offsets);
375: }
376: needOffsets = false;
377: }
378: }
379:
380: /**
381: * Overrides this method to indicate that getLogSize and writeToLog can
382: * change the memory size of the LN. The size returned by ObsoleteOffsets
383: * getExtraMemorySize can change when getOffsets is called by getLogSize or
384: * writeToLog, when the trackedSummary field is non-null.
385: */
386: boolean canMemorySizeChangeDuringLogging() {
387: return true;
388: }
389:
390: /**
391: * Overrides this method to add space occupied by this object's fields.
392: */
393: public long getMemorySizeIncludedByParent() {
394: return super .getMemorySizeIncludedByParent()
395: + (MemoryBudget.FILESUMMARYLN_OVERHEAD - MemoryBudget.LN_OVERHEAD)
396: + obsoleteOffsets.getExtraMemorySize();
397: }
398:
399: /**
400: * Clear out the obsoleteOffsets to save memory when the LN is deleted.
401: */
402: void makeDeleted() {
403: super .makeDeleted();
404: obsoleteOffsets = new PackedOffsets();
405: }
406: }
|