blob: 58dbae45786e8e5f73581ea3c24ea064cc2c980c [file] [log] [blame]
Darin Petkovf1e85e42010-06-10 15:59:53 -07001// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "counter.h"
6
7#include <sys/file.h>
8
9#include <base/eintr_wrapper.h>
10#include <base/logging.h>
11
12namespace chromeos_metrics {
13
14// TaggedCounter::Record implementation.
15void TaggedCounter::Record::Init(int tag, int count) {
16 tag_ = tag;
17 count_ = (count > 0) ? count : 0;
18}
19
20void TaggedCounter::Record::Add(int count) {
21 if (count <= 0)
22 return;
23
24 count_ += count;
25
26 // Saturates on postive overflow.
27 if (count_ < 0) {
28 count_ = INT_MAX;
29 }
30}
31
32// TaggedCounter implementation.
33TaggedCounter::TaggedCounter()
34 : filename_(NULL), reporter_(NULL), reporter_handle_(NULL),
35 record_state_(kRecordInvalid) {}
36
37TaggedCounter::~TaggedCounter() {}
38
39void TaggedCounter::Init(const char* filename,
40 Reporter reporter, void* reporter_handle) {
41 DCHECK(filename);
42 filename_ = filename;
43 reporter_ = reporter;
44 reporter_handle_ = reporter_handle;
45 record_state_ = kRecordInvalid;
46}
47
48void TaggedCounter::Update(int tag, int count) {
49 UpdateInternal(tag,
50 count,
51 false); // No flush.
52}
53
54void TaggedCounter::Flush() {
55 UpdateInternal(0, // tag
56 0, // count
57 true); // Do flush.
58}
59
60void TaggedCounter::UpdateInternal(int tag, int count, bool flush) {
Darin Petkov1bb904e2010-06-16 15:58:06 -070061 if (flush) {
62 // Flushing but record is null, so nothing to do.
63 if (record_state_ == kRecordNull)
64 return;
65 } else {
66 // If there's no new data and the last record in the aggregation
67 // file is with the same tag, there's nothing to do.
68 if (count <= 0 && record_state_ == kRecordValid && record_.tag() == tag)
69 return;
70 }
Darin Petkovf1e85e42010-06-10 15:59:53 -070071
72 DLOG(INFO) << "tag: " << tag << " count: " << count << " flush: " << flush;
73 DCHECK(filename_);
74
75 // NOTE: The assumption is that this TaggedCounter object is the
76 // sole owner of the persistent storage file so no locking is
77 // necessary.
78 int fd = HANDLE_EINTR(open(filename_, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR));
79 if (fd < 0) {
80 PLOG(WARNING) << "Unable to open the persistent counter file";
81 return;
82 }
83
84 ReadRecord(fd);
85 ReportRecord(tag, flush);
Darin Petkov1bb904e2010-06-16 15:58:06 -070086 UpdateRecord(tag, count, flush);
Darin Petkovf1e85e42010-06-10 15:59:53 -070087 WriteRecord(fd);
88
89 HANDLE_EINTR(close(fd));
90}
91
92void TaggedCounter::ReadRecord(int fd) {
93 if (record_state_ != kRecordInvalid)
94 return;
95
96 if (HANDLE_EINTR(read(fd, &record_, sizeof(record_))) == sizeof(record_)) {
Darin Petkov1bb904e2010-06-16 15:58:06 -070097 if (record_.count() >= 0) {
Darin Petkovf1e85e42010-06-10 15:59:53 -070098 record_state_ = kRecordValid;
99 return;
100 }
101 // This shouldn't happen normally unless somebody messed with the
102 // persistent storage file.
103 NOTREACHED();
104 record_state_ = kRecordNullDirty;
105 return;
106 }
107
108 record_state_ = kRecordNull;
109}
110
111void TaggedCounter::ReportRecord(int tag, bool flush) {
112 // If no valid record, there's nothing to report.
113 if (record_state_ != kRecordValid) {
Darin Petkov1bb904e2010-06-16 15:58:06 -0700114 DCHECK_EQ(record_state_, kRecordNull);
Darin Petkovf1e85e42010-06-10 15:59:53 -0700115 return;
116 }
117
118 // If the current record has the same tag as the new tag, it's not
119 // ready to be reported yet.
120 if (!flush && record_.tag() == tag)
121 return;
122
123 if (reporter_) {
124 reporter_(reporter_handle_, record_.tag(), record_.count());
125 }
126 record_state_ = kRecordNullDirty;
127}
128
Darin Petkov1bb904e2010-06-16 15:58:06 -0700129void TaggedCounter::UpdateRecord(int tag, int count, bool flush) {
130 if (flush) {
131 DCHECK(record_state_ == kRecordNull || record_state_ == kRecordNullDirty);
Darin Petkovf1e85e42010-06-10 15:59:53 -0700132 return;
Darin Petkov1bb904e2010-06-16 15:58:06 -0700133 }
Darin Petkovf1e85e42010-06-10 15:59:53 -0700134
135 switch (record_state_) {
136 case kRecordNull:
137 case kRecordNullDirty:
138 // Current record is null, starting a new record.
139 record_.Init(tag, count);
140 record_state_ = kRecordValidDirty;
141 break;
142
143 case kRecordValid:
144 // If there's an existing record for the current tag,
145 // accumulates the counts.
146 DCHECK_EQ(record_.tag(), tag);
Darin Petkov1bb904e2010-06-16 15:58:06 -0700147 if (count > 0) {
148 record_.Add(count);
149 record_state_ = kRecordValidDirty;
150 }
Darin Petkovf1e85e42010-06-10 15:59:53 -0700151 break;
152
153 default:
154 NOTREACHED();
155 }
156}
157
158void TaggedCounter::WriteRecord(int fd) {
159 switch (record_state_) {
160 case kRecordNullDirty:
161 // Truncates the aggregation file to discard the record.
162 PLOG_IF(WARNING, HANDLE_EINTR(ftruncate(fd, 0)) != 0);
163 record_state_ = kRecordNull;
164 break;
165
166 case kRecordValidDirty:
167 // Updates the accumulator record in the file if there's new data.
168 PLOG_IF(WARNING, HANDLE_EINTR(lseek(fd, 0, SEEK_SET)) != 0);
169 PLOG_IF(WARNING,
170 HANDLE_EINTR(write(fd, &record_, sizeof(record_))) !=
171 sizeof(record_));
172 record_state_ = kRecordValid;
173 break;
174
175 case kRecordNull:
176 case kRecordValid:
177 // Nothing to do.
178 break;
179
180 default:
181 NOTREACHED();
182 }
183}
184
185} // namespace chromeos_metrics