blob: 1cfcb51d42071fa66d2d907b03231abadac8cc08 [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#ifndef METRICS_COUNTER_H_
6#define METRICS_COUNTER_H_
7
Darin Petkovcd8c3172010-06-24 10:13:54 -07008#include <base/basictypes.h>
Darin Petkovf1e85e42010-06-10 15:59:53 -07009#include <gtest/gtest_prod.h> // for FRIEND_TEST
10
11namespace chromeos_metrics {
12
13// TaggedCounter maintains a persistent storage (i.e., a file)
14// aggregation counter for a given tag (e.g., day, hour) that survives
15// system shutdowns, reboots and crashes, as well as daemon process
16// restarts. The counter object is initialized by pointing to the
17// persistent storage file and providing a callback used for reporting
18// aggregated data. The counter can then be updated with additional
19// event counts. The aggregated count is reported through the
20// callback when the counter is explicitly flushed or when data for a
21// new tag arrives.
Darin Petkovcd8c3172010-06-24 10:13:54 -070022//
23// The primary reason for using an interface is to allow easier unit
24// testing in clients through mocking thus avoiding file access and
25// callbacks. Of course, it also enables alternative implementations
26// of the counter with additional features.
Darin Petkovf1e85e42010-06-10 15:59:53 -070027class TaggedCounterInterface {
28 public:
29 // Callback type used for reporting aggregated or flushed data.
30 // Once this callback is invoked by the counter, the reported
Darin Petkov1bb904e2010-06-16 15:58:06 -070031 // aggregated data is discarded.
Darin Petkovf1e85e42010-06-10 15:59:53 -070032 //
33 // |handle| is the |reporter_handle| pointer passed through Init.
34 // |tag| is the tag associated with the aggregated count.
35 // |count| is aggregated count.
Darin Petkovcd8c3172010-06-24 10:13:54 -070036 typedef void (*Reporter)(void* handle, int32 tag, int32 count);
Darin Petkovf1e85e42010-06-10 15:59:53 -070037
38 virtual ~TaggedCounterInterface() {}
39
40 // Initializes the counter by providing the persistent storage
41 // location |filename| and a |reporter| callback for reporting
42 // aggregated counts. |reporter_handle| is sent to the |reporter|
43 // along with the aggregated counts.
44 //
45 // NOTE: The assumption is that this object is the sole owner of the
46 // persistent storage file so no locking is currently implemented.
47 virtual void Init(const char* filename,
48 Reporter reporter, void* reporter_handle) = 0;
49
50 // Adds |count| of events for the given |tag|. If there's an
51 // existing aggregated count for a different tag, it's reported
52 // through the reporter callback and discarded.
Darin Petkovcd8c3172010-06-24 10:13:54 -070053 virtual void Update(int32 tag, int32 count) = 0;
Darin Petkovf1e85e42010-06-10 15:59:53 -070054
55 // Reports the current aggregated count (if any) through the
56 // reporter callback and discards it.
57 virtual void Flush() = 0;
58};
59
60class TaggedCounter : public TaggedCounterInterface {
61 public:
62 TaggedCounter();
63 ~TaggedCounter();
64
65 // Implementation of interface methods.
66 void Init(const char* filename, Reporter reporter, void* reporter_handle);
Darin Petkovcd8c3172010-06-24 10:13:54 -070067 void Update(int32 tag, int32 count);
Darin Petkovf1e85e42010-06-10 15:59:53 -070068 void Flush();
69
70 private:
71 friend class RecordTest;
72 friend class TaggedCounterTest;
73 FRIEND_TEST(TaggedCounterTest, BadFileLocation);
74 FRIEND_TEST(TaggedCounterTest, Flush);
75 FRIEND_TEST(TaggedCounterTest, InitFromFile);
76 FRIEND_TEST(TaggedCounterTest, Update);
77
78 // The current tag/count record is cached by the counter object to
79 // avoid potentially unnecessary I/O. The cached record can be in
80 // one of the following states:
81 enum RecordState {
82 kRecordInvalid, // Invalid record, sync from persistent storage needed.
83 kRecordNull, // No current record, persistent storage synced.
84 kRecordNullDirty, // No current record, persistent storage is invalid.
85 kRecordValid, // Current record valid, persistent storage synced.
86 kRecordValidDirty // Current record valid, persistent storage is invalid.
87 };
88
89 // Defines the tag/count record. Objects of this class are synced
90 // with the persistent storage through binary reads/writes.
91 class Record {
92 public:
93 // Creates a new Record with |tag_| and |count_| reset to 0.
94 Record() : tag_(0), count_(0) {}
95
96 // Initializes with |tag| and |count|. If |count| is negative,
97 // |count_| is set to 0.
Darin Petkovcd8c3172010-06-24 10:13:54 -070098 void Init(int32 tag, int32 count);
Darin Petkovf1e85e42010-06-10 15:59:53 -070099
100 // Adds |count| to the current |count_|. Negative |count| is
101 // ignored. In case of positive overflow, |count_| is saturated to
Darin Petkovcd8c3172010-06-24 10:13:54 -0700102 // kint32max.
103 void Add(int32 count);
Darin Petkovf1e85e42010-06-10 15:59:53 -0700104
Darin Petkovcd8c3172010-06-24 10:13:54 -0700105 int32 tag() const { return tag_; }
106 int32 count() const { return count_; }
Darin Petkovf1e85e42010-06-10 15:59:53 -0700107
108 private:
Darin Petkovcd8c3172010-06-24 10:13:54 -0700109 int32 tag_;
110 int32 count_;
Darin Petkovf1e85e42010-06-10 15:59:53 -0700111 };
112
113 // Implementation of the Update and Flush methods. Goes through the
114 // necessary steps to read, report, update, and sync the aggregated
115 // record.
Darin Petkovcd8c3172010-06-24 10:13:54 -0700116 void UpdateInternal(int32 tag, int32 count, bool flush);
Darin Petkovf1e85e42010-06-10 15:59:53 -0700117
118 // If the current cached record is invalid, reads it from persistent
119 // storage specified through file descriptor |fd| and updates the
120 // cached record state to either null, or valid depending on the
121 // persistent storage contents.
122 void ReadRecord(int fd);
123
124 // If there's an existing valid record and either |flush| is true,
125 // or the new |tag| is different than the old one, reports the
126 // aggregated data through the reporter callback and resets the
127 // cached record.
Darin Petkovcd8c3172010-06-24 10:13:54 -0700128 void ReportRecord(int32 tag, bool flush);
Darin Petkovf1e85e42010-06-10 15:59:53 -0700129
130 // Updates the cached record given the new |tag| and |count|. This
131 // method expects either a null cached record, or a valid cached
Darin Petkov1bb904e2010-06-16 15:58:06 -0700132 // record with the same tag as |tag|. If |flush| is true, the method
133 // asserts that the cached record is null and returns.
Darin Petkovcd8c3172010-06-24 10:13:54 -0700134 void UpdateRecord(int32 tag, int32 count, bool flush);
Darin Petkovf1e85e42010-06-10 15:59:53 -0700135
136 // If the cached record state is dirty, updates the persistent
137 // storage specified through file descriptor |fd| and switches the
138 // record state to non-dirty.
139 void WriteRecord(int fd);
140
141 // Persistent storage file path.
142 const char* filename_;
143
144 // Aggregated data reporter callback and handle to pass-through.
145 Reporter reporter_;
146 void* reporter_handle_;
147
148 // Current cached aggregation record.
149 Record record_;
150
151 // Current cached aggregation record state.
152 RecordState record_state_;
153};
154
155} // namespace chromeos_metrics
156
157#endif // METRICS_COUNTER_H_