blob: 4de1db1051fb7570d07f5413b04adee46b87a49d [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
Darin Petkovcd8c3172010-06-24 10:13:54 -07007#include <fcntl.h>
Darin Petkovf1e85e42010-06-10 15:59:53 -07008
9#include <base/eintr_wrapper.h>
10#include <base/logging.h>
11
12namespace chromeos_metrics {
13
14// TaggedCounter::Record implementation.
Darin Petkovcd8c3172010-06-24 10:13:54 -070015void TaggedCounter::Record::Init(int32 tag, int32 count) {
Darin Petkovf1e85e42010-06-10 15:59:53 -070016 tag_ = tag;
17 count_ = (count > 0) ? count : 0;
18}
19
Darin Petkovcd8c3172010-06-24 10:13:54 -070020void TaggedCounter::Record::Add(int32 count) {
Darin Petkovf1e85e42010-06-10 15:59:53 -070021 if (count <= 0)
22 return;
23
Darin Petkovcd8c3172010-06-24 10:13:54 -070024 // Saturates on positive overflow.
25 int64 new_count = static_cast<int64>(count_) + static_cast<int64>(count);
26 if (new_count > kint32max)
27 count_ = kint32max;
28 else
29 count_ = static_cast<int32>(new_count);
Darin Petkovf1e85e42010-06-10 15:59:53 -070030}
31
32// TaggedCounter implementation.
33TaggedCounter::TaggedCounter()
Darin Petkovcd8c3172010-06-24 10:13:54 -070034 : filename_(NULL),
35 reporter_(NULL),
36 reporter_handle_(NULL),
Darin Petkovf1e85e42010-06-10 15:59:53 -070037 record_state_(kRecordInvalid) {}
38
39TaggedCounter::~TaggedCounter() {}
40
41void TaggedCounter::Init(const char* filename,
42 Reporter reporter, void* reporter_handle) {
43 DCHECK(filename);
44 filename_ = filename;
45 reporter_ = reporter;
46 reporter_handle_ = reporter_handle;
47 record_state_ = kRecordInvalid;
48}
49
Darin Petkovcd8c3172010-06-24 10:13:54 -070050void TaggedCounter::Update(int32 tag, int32 count) {
Darin Petkovf1e85e42010-06-10 15:59:53 -070051 UpdateInternal(tag,
52 count,
53 false); // No flush.
54}
55
56void TaggedCounter::Flush() {
57 UpdateInternal(0, // tag
58 0, // count
59 true); // Do flush.
60}
61
Darin Petkovcd8c3172010-06-24 10:13:54 -070062void TaggedCounter::UpdateInternal(int32 tag, int32 count, bool flush) {
Darin Petkov1bb904e2010-06-16 15:58:06 -070063 if (flush) {
64 // Flushing but record is null, so nothing to do.
65 if (record_state_ == kRecordNull)
66 return;
67 } else {
68 // If there's no new data and the last record in the aggregation
69 // file is with the same tag, there's nothing to do.
70 if (count <= 0 && record_state_ == kRecordValid && record_.tag() == tag)
71 return;
72 }
Darin Petkovf1e85e42010-06-10 15:59:53 -070073
74 DLOG(INFO) << "tag: " << tag << " count: " << count << " flush: " << flush;
75 DCHECK(filename_);
76
77 // NOTE: The assumption is that this TaggedCounter object is the
78 // sole owner of the persistent storage file so no locking is
79 // necessary.
80 int fd = HANDLE_EINTR(open(filename_, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR));
81 if (fd < 0) {
82 PLOG(WARNING) << "Unable to open the persistent counter file";
83 return;
84 }
85
86 ReadRecord(fd);
87 ReportRecord(tag, flush);
Darin Petkov1bb904e2010-06-16 15:58:06 -070088 UpdateRecord(tag, count, flush);
Darin Petkovf1e85e42010-06-10 15:59:53 -070089 WriteRecord(fd);
90
91 HANDLE_EINTR(close(fd));
92}
93
94void TaggedCounter::ReadRecord(int fd) {
95 if (record_state_ != kRecordInvalid)
96 return;
97
98 if (HANDLE_EINTR(read(fd, &record_, sizeof(record_))) == sizeof(record_)) {
Darin Petkov1bb904e2010-06-16 15:58:06 -070099 if (record_.count() >= 0) {
Darin Petkovf1e85e42010-06-10 15:59:53 -0700100 record_state_ = kRecordValid;
101 return;
102 }
103 // This shouldn't happen normally unless somebody messed with the
104 // persistent storage file.
105 NOTREACHED();
106 record_state_ = kRecordNullDirty;
107 return;
108 }
Darin Petkovf1e85e42010-06-10 15:59:53 -0700109 record_state_ = kRecordNull;
110}
111
Darin Petkovcd8c3172010-06-24 10:13:54 -0700112void TaggedCounter::ReportRecord(int32 tag, bool flush) {
Darin Petkovf1e85e42010-06-10 15:59:53 -0700113 // If no valid record, there's nothing to report.
114 if (record_state_ != kRecordValid) {
Darin Petkov1bb904e2010-06-16 15:58:06 -0700115 DCHECK_EQ(record_state_, kRecordNull);
Darin Petkovf1e85e42010-06-10 15:59:53 -0700116 return;
117 }
118
119 // If the current record has the same tag as the new tag, it's not
120 // ready to be reported yet.
121 if (!flush && record_.tag() == tag)
122 return;
123
124 if (reporter_) {
125 reporter_(reporter_handle_, record_.tag(), record_.count());
126 }
127 record_state_ = kRecordNullDirty;
128}
129
Darin Petkovcd8c3172010-06-24 10:13:54 -0700130void TaggedCounter::UpdateRecord(int32 tag, int32 count, bool flush) {
Darin Petkov1bb904e2010-06-16 15:58:06 -0700131 if (flush) {
132 DCHECK(record_state_ == kRecordNull || record_state_ == kRecordNullDirty);
Darin Petkovf1e85e42010-06-10 15:59:53 -0700133 return;
Darin Petkov1bb904e2010-06-16 15:58:06 -0700134 }
Darin Petkovf1e85e42010-06-10 15:59:53 -0700135
136 switch (record_state_) {
137 case kRecordNull:
138 case kRecordNullDirty:
139 // Current record is null, starting a new record.
140 record_.Init(tag, count);
141 record_state_ = kRecordValidDirty;
142 break;
143
144 case kRecordValid:
145 // If there's an existing record for the current tag,
146 // accumulates the counts.
147 DCHECK_EQ(record_.tag(), tag);
Darin Petkov1bb904e2010-06-16 15:58:06 -0700148 if (count > 0) {
149 record_.Add(count);
150 record_state_ = kRecordValidDirty;
151 }
Darin Petkovf1e85e42010-06-10 15:59:53 -0700152 break;
153
154 default:
155 NOTREACHED();
156 }
157}
158
159void TaggedCounter::WriteRecord(int fd) {
160 switch (record_state_) {
161 case kRecordNullDirty:
162 // Truncates the aggregation file to discard the record.
163 PLOG_IF(WARNING, HANDLE_EINTR(ftruncate(fd, 0)) != 0);
164 record_state_ = kRecordNull;
165 break;
166
167 case kRecordValidDirty:
168 // Updates the accumulator record in the file if there's new data.
169 PLOG_IF(WARNING, HANDLE_EINTR(lseek(fd, 0, SEEK_SET)) != 0);
170 PLOG_IF(WARNING,
171 HANDLE_EINTR(write(fd, &record_, sizeof(record_))) !=
172 sizeof(record_));
173 record_state_ = kRecordValid;
174 break;
175
176 case kRecordNull:
177 case kRecordValid:
178 // Nothing to do.
179 break;
180
181 default:
182 NOTREACHED();
183 }
184}
185
Ken Mixterccd84c02010-08-16 19:57:13 -0700186FrequencyCounter::FrequencyCounter() : cycle_duration_(1) {
187}
188
189FrequencyCounter::~FrequencyCounter() {
190}
191
192void FrequencyCounter::Init(const char* filename,
193 TaggedCounterInterface::Reporter reporter,
194 void* reporter_handle,
195 time_t cycle_duration) {
196 // Allow tests to inject tagged_counter_ dependency.
197 if (tagged_counter_.get() == NULL) {
198 tagged_counter_.reset(new TaggedCounter());
199 }
200 tagged_counter_->Init(filename, reporter, reporter_handle);
201 DCHECK(cycle_duration > 0);
202 cycle_duration_ = cycle_duration;
203}
204
205void FrequencyCounter::UpdateInternal(int32 count, time_t now) {
206 DCHECK(tagged_counter_.get() != NULL);
207 tagged_counter_->Update(GetCycleNumber(now), count);
208}
209
210int32 FrequencyCounter::GetCycleNumber(time_t now) {
211 return now / cycle_duration_;
212}
213
Darin Petkovf1e85e42010-06-10 15:59:53 -0700214} // namespace chromeos_metrics