001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: UtilizationTracker.java,v 1.19.2.4 2008/01/07 15:14:08 cwl Exp $
007: */
008:
009: package com.sleepycat.je.cleaner;
010:
011: import java.util.ArrayList;
012: import java.util.List;
013:
014: import com.sleepycat.je.DatabaseException;
015: import com.sleepycat.je.dbi.EnvironmentImpl;
016: import com.sleepycat.je.dbi.MemoryBudget;
017: import com.sleepycat.je.log.LogEntryType;
018: import com.sleepycat.je.utilint.DbLsn;
019:
020: /**
021: * Tracks changes to the utilization profile since the last checkpoint.
022: *
023: * <p>All changes to this object occur must under the log write latch. It is
024: * possible to read tracked info without holding the latch. This is done by
025: * the cleaner when selecting a file and by the checkpointer when determining
026: * what FileSummaryLNs need to be written. To read tracked info outside the
027: * log write latch, call getTrackedFile or getTrackedFiles. activateCleaner
028: * can also be called outside the latch.</p>
029: */
030: public class UtilizationTracker {
031:
032: private EnvironmentImpl env;
033: private Cleaner cleaner;
034: private List files;
035: private long activeFile;
036: private TrackedFileSummary[] snapshot;
037: private long bytesSinceActivate;
038:
039: /**
040: * Creates an empty tracker. The cleaner field of the environment object
041: * must be initialized before using this constructor.
042: */
043: public UtilizationTracker(EnvironmentImpl env)
044: throws DatabaseException {
045:
046: this (env, env.getCleaner());
047: }
048:
049: /**
050: * Constructor used by the cleaner constructor, prior to setting the
051: * cleaner field of the environment.
052: */
053: UtilizationTracker(EnvironmentImpl env, Cleaner cleaner)
054: throws DatabaseException {
055:
056: assert cleaner != null;
057: this .env = env;
058: this .cleaner = cleaner;
059: files = new ArrayList();
060: snapshot = new TrackedFileSummary[0];
061: activeFile = -1;
062: }
063:
064: public EnvironmentImpl getEnvironment() {
065: return env;
066: }
067:
068: /**
069: * Evicts tracked detail if the budget for the tracker is exceeded. Evicts
070: * only one file summary LN at most to keep eviction batches small.
071: * Returns the number of bytes freed.
072: *
073: * <p>When flushFileSummary is called, the TrackedFileSummary is cleared via
074: * its reset method, which is called by FileSummaryLN.writeToLog. This is
075: * how memory is subtracted from the budget.</p>
076: */
077: public long evictMemory() throws DatabaseException {
078:
079: /* If not tracking detail, there is nothing to evict. */
080: if (!cleaner.trackDetail) {
081: return 0;
082: }
083:
084: /*
085: * Do not start eviction until after recovery, since the
086: * UtilizationProfile will not be initialized properly. UP
087: * initialization requires that all LNs have been replayed.
088: */
089: if (!env.isOpen()) {
090: return 0;
091: }
092:
093: MemoryBudget mb = env.getMemoryBudget();
094: long totalEvicted = 0;
095: long totalBytes = 0;
096: int largestBytes = 0;
097: TrackedFileSummary bestFile = null;
098:
099: /*
100: * Use a local variable to access the array since the snapshot
101: * field can be changed by other threads.
102: */
103: TrackedFileSummary[] a = snapshot;
104: for (int i = 0; i < a.length; i += 1) {
105:
106: TrackedFileSummary tfs = a[i];
107: int mem = tfs.getMemorySize();
108: totalBytes += mem;
109:
110: if (mem > largestBytes && tfs.getAllowFlush()) {
111: largestBytes = mem;
112: bestFile = tfs;
113: }
114: }
115:
116: if (bestFile != null && totalBytes > mb.getTrackerBudget()) {
117: env.getUtilizationProfile().flushFileSummary(bestFile);
118: totalEvicted += largestBytes;
119: }
120: return totalEvicted;
121: }
122:
123: /**
124: * Wakeup the cleaner thread and reset the log byte counter.
125: */
126: public void activateCleaner() {
127: env.getCleaner().wakeup();
128: bytesSinceActivate = 0;
129: }
130:
131: /**
132: * Returns a snapshot of the files being tracked as of the last time a
133: * log entry was added. The summary info returned is the delta since the
134: * last checkpoint, not the grand totals, and is approximate since it is
135: * changing in real time. This method may be called without holding the
136: * log write latch.
137: *
138: * <p>If files are added or removed from the list of tracked files in real
139: * time, the returned array will not be changed since it is a snapshot.
140: * But the objects contained in the array are live and will be updated in
141: * real time under the log write latch. The array and the objects in the
142: * array should not be modified by the caller.</p>
143: */
144: public TrackedFileSummary[] getTrackedFiles() {
145: return snapshot;
146: }
147:
148: /**
149: * Returns one file from the snapshot of tracked files, or null if the
150: * given file number is not in the snapshot array.
151: * @see #getTrackedFiles
152: */
153: public TrackedFileSummary getTrackedFile(long fileNum) {
154:
155: /*
156: * Use a local variable to access the array since the snapshot field
157: * can be changed by other threads.
158: */
159: TrackedFileSummary[] a = snapshot;
160: for (int i = 0; i < a.length; i += 1) {
161: if (a[i].getFileNumber() == fileNum) {
162: return a[i];
163: }
164: }
165: return null;
166: }
167:
168: /**
169: * Counts the addition of all new log entries including LNs, and returns
170: * whether the cleaner should be woken.
171: *
172: * <p>Must be called under the log write latch.</p>
173: */
174: public boolean countNewLogEntry(long lsn, LogEntryType type,
175: int size) {
176:
177: TrackedFileSummary file = getFile(DbLsn.getFileNumber(lsn));
178: file.totalCount += 1;
179: file.totalSize += size;
180: if (type.isNodeType()) {
181: if (inArray(type, LogEntryType.IN_TYPES)) {
182: file.totalINCount += 1;
183: file.totalINSize += size;
184: } else {
185: file.totalLNCount += 1;
186: file.totalLNSize += size;
187: }
188: }
189: bytesSinceActivate += size;
190: return (bytesSinceActivate >= env.getCleaner().cleanerBytesInterval);
191: }
192:
193: /**
194: * Counts a node that has become obsolete and tracks the LSN offset, if
195: * non-zero, to avoid a lookup during cleaning.
196: *
197: * <p>A zero LSN offset is used as a special value when obsolete offset
198: * tracking is not desired. [#15365] The file header entry (at offset
199: * zero) is never counted as obsolete, it is assumed to be obsolete by the
200: * cleaner.</p>
201: *
202: * <p>This method should only be called for LNs and INs (i.e, only for
203: * nodes). If type is null we assume it is an LN.</p>
204: *
205: * <p>Must be called under the log write latch.</p>
206: */
207: public void countObsoleteNode(long lsn, LogEntryType type, int size) {
208:
209: TrackedFileSummary file = getFile(DbLsn.getFileNumber(lsn));
210:
211: countOneNode(file, type, size);
212:
213: long offset = DbLsn.getFileOffset(lsn);
214: if (offset != 0) {
215: file.trackObsolete(offset);
216: }
217: }
218:
219: /**
220: * Counts as countObsoleteNode does, but since the LSN may be inexact, does
221: * not track the obsolete LSN offset.
222: *
223: * <p>This method should only be called for LNs and INs (i.e, only for
224: * nodes). If type is null we assume it is an LN.</p>
225: *
226: * <p>Must be called under the log write latch.</p>
227: */
228: public void countObsoleteNodeInexact(long lsn, LogEntryType type,
229: int size) {
230:
231: TrackedFileSummary file = getFile(DbLsn.getFileNumber(lsn));
232:
233: countOneNode(file, type, size);
234: }
235:
236: /**
237: * Counts an obsolete node by incrementing the obsolete count and size.
238: */
239: private void countOneNode(TrackedFileSummary file,
240: LogEntryType type, int size) {
241:
242: if (type == null || type.isNodeType()) {
243: if (type == null || !inArray(type, LogEntryType.IN_TYPES)) {
244: file.obsoleteLNCount += 1;
245: /* The size is optional when tracking obsolete LNs. */
246: if (size > 0) {
247: file.obsoleteLNSize += size;
248: file.obsoleteLNSizeCounted += 1;
249: }
250: } else {
251: file.obsoleteINCount += 1;
252: /* The size is not allowed when tracking obsolete INs. */
253: assert size == 0;
254: }
255: }
256: }
257:
258: /**
259: * Adds changes from a given TrackedFileSummary.
260: *
261: * <p>Must be called under the log write latch.</p>
262: */
263: public void addSummary(long fileNumber, TrackedFileSummary other) {
264:
265: TrackedFileSummary file = getFile(fileNumber);
266: file.addTrackedSummary(other);
267: }
268:
269: /**
270: * Returns a tracked summary for the given file which will not be flushed.
271: * Used for watching changes that occur while a file is being cleaned.
272: */
273: public TrackedFileSummary getUnflushableTrackedSummary(long fileNum)
274: throws DatabaseException {
275:
276: TrackedFileSummary file = getFile(fileNum);
277: file.setAllowFlush(false);
278: return file;
279: }
280:
281: /**
282: * Returns a tracked file for the given file number, adding an empty one
283: * if the file is not already being tracked.
284: *
285: * <p>Must be called under the log write latch.</p>
286: */
287: private TrackedFileSummary getFile(long fileNum) {
288:
289: if (activeFile < fileNum) {
290: activeFile = fileNum;
291: }
292: int size = files.size();
293: for (int i = 0; i < size; i += 1) {
294: TrackedFileSummary file = (TrackedFileSummary) files.get(i);
295: if (file.getFileNumber() == fileNum) {
296: return file;
297: }
298: }
299:
300: /*
301: * Create a new tracking object and take a snapshot of the updated file
302: * list.
303: */
304: TrackedFileSummary file = new TrackedFileSummary(this , fileNum,
305: cleaner.trackDetail);
306:
307: files.add(file);
308: takeSnapshot();
309: return file;
310: }
311:
312: /**
313: * Called after the FileSummaryLN is written to the log during checkpoint.
314: *
315: * <p>We keep the active file summary in the tracked file list, but we
316: * remove older files to prevent unbounded growth of the list.</p>
317: *
318: * <p>Must be called under the log write latch.</p>
319: */
320: void resetFile(TrackedFileSummary file) {
321:
322: if (file.getFileNumber() < activeFile && file.getAllowFlush()) {
323: files.remove(file);
324: takeSnapshot();
325: }
326: }
327:
328: /**
329: * Takes a snapshot of the tracked file list.
330: *
331: * <p>Must be called under the log write latch.</p>
332: */
333: private void takeSnapshot() {
334: /*
335: * Only assign to the snapshot field with a populated array, since it
336: * will be accessed by other threads.
337: */
338: TrackedFileSummary[] a = new TrackedFileSummary[files.size()];
339: files.toArray(a);
340: snapshot = a;
341: }
342:
343: /**
344: * Returns whether an object reference is in an array.
345: */
346: private boolean inArray(Object o, Object[] a) {
347:
348: for (int i = 0; i < a.length; i += 1) {
349: if (a[i] == o) {
350: return true;
351: }
352: }
353: return false;
354: }
355: }
|