Line data Source code
1 : // Load Balancing -- class used to handle the load average file
2 : // Copyright (c) 2017-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // This program is free software; you can redistribute it and/or modify
5 : // it under the terms of the GNU General Public License as published by
6 : // the Free Software Foundation; either version 2 of the License, or
7 : // (at your option) any later version.
8 : //
9 : // This program is distributed in the hope that it will be useful,
10 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : // GNU General Public License for more details.
13 : //
14 : // You should have received a copy of the GNU General Public License
15 : // along with this program; if not, write to the Free Software
16 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 :
18 :
19 : // self
20 : //
21 : #include "snapwebsites/loadavg.h"
22 :
23 :
24 : // our lib
25 : //
26 : #include "snapwebsites/log.h"
27 :
28 :
29 : // snapdev lib
30 : //
31 : #include <snapdev/raii_generic_deleter.h>
32 :
33 :
34 : // C lib
35 : //
36 : #include <fcntl.h>
37 : #include <sys/file.h>
38 : #include <sys/stat.h>
39 : #include <sys/types.h>
40 : #include <unistd.h>
41 :
42 :
43 : // C++ lib
44 : //
45 : #include <memory>
46 :
47 :
48 : // last include
49 : //
50 : #include <snapdev/poison.h>
51 :
52 :
53 :
54 : namespace snap
55 : {
56 :
57 :
58 : namespace
59 : {
60 :
61 :
62 2 : std::string g_filename;
63 :
64 :
65 :
66 :
67 : int const LOADAVG_VERSION = 1;
68 :
69 : struct loadavg_magic
70 : {
71 : char f_name[4]{'L', 'A', 'V', 'G'}; // 'LAVG'
72 : uint16_t f_version = LOADAVG_VERSION; // 1+ representing the version
73 : };
74 :
75 :
76 : } // no name namespace
77 :
78 :
79 0 : bool loadavg_file::load()
80 : {
81 : // open the file
82 : //
83 0 : raii_fd_t safe_fd(open(g_filename.c_str(), O_RDONLY));
84 0 : if(!safe_fd)
85 : {
86 0 : return false;
87 : }
88 :
89 : // lock the file in share mode (multiple read, no writes)
90 : //
91 0 : if(flock(safe_fd.get(), LOCK_SH) != 0)
92 : {
93 0 : return false;
94 : }
95 :
96 : // verify the magic
97 : //
98 0 : loadavg_magic magic;
99 0 : if(read(safe_fd.get(), &magic, sizeof(magic)) != sizeof(magic))
100 : {
101 0 : return false;
102 : }
103 0 : if(magic.f_name[0] != 'L'
104 0 : || magic.f_name[1] != 'A'
105 0 : || magic.f_name[2] != 'V'
106 0 : || magic.f_name[3] != 'G'
107 0 : || magic.f_version != LOADAVG_VERSION)
108 : {
109 0 : return false;
110 : }
111 :
112 : // load each item
113 : //
114 : for(;;)
115 : {
116 0 : loadavg_item item;
117 0 : ssize_t const r(read(safe_fd.get(), &item, sizeof(item)));
118 0 : if(r < 0)
119 : {
120 0 : return false;
121 : }
122 0 : if(r == 0)
123 : {
124 : // we got EOF
125 0 : break;
126 : }
127 0 : f_items.push_back(item);
128 0 : }
129 :
130 : // it worked
131 : //
132 0 : return true;
133 : }
134 :
135 :
136 0 : bool loadavg_file::save() const
137 : {
138 : // open the file
139 : //
140 0 : raii_fd_t safe_fd(open(g_filename.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
141 0 : if(!safe_fd)
142 : {
143 0 : return false;
144 : }
145 :
146 : // lock the file in share mode (multiple read, no writes)
147 : //
148 0 : if(flock(safe_fd.get(), LOCK_EX) != 0)
149 : {
150 0 : return false;
151 : }
152 :
153 : // write the magic each time (in case the version changed
154 : // or we are creating a new file)
155 : //
156 0 : loadavg_magic magic;
157 0 : if(write(safe_fd.get(), &magic, sizeof(magic)) != sizeof(magic))
158 : {
159 0 : return false;
160 : }
161 :
162 : // write each item
163 : //
164 0 : for(auto const & item : f_items)
165 : {
166 0 : ssize_t const r(write(safe_fd.get(), &item, sizeof(item)));
167 0 : if(r < 0)
168 : {
169 0 : return false;
170 : }
171 : }
172 :
173 : // it worked
174 : //
175 0 : return true;
176 : }
177 :
178 :
179 0 : void loadavg_file::add(loadavg_item const & new_item)
180 : {
181 : auto const & it(std::find_if(
182 : f_items.begin(),
183 : f_items.end(),
184 0 : [new_item](auto const & item)
185 : {
186 0 : return item.f_address == new_item.f_address;
187 0 : }));
188 :
189 0 : if(it == f_items.end())
190 : {
191 0 : f_items.push_back(new_item);
192 : }
193 : else
194 : {
195 : // replace existing item with new avg and timestamp
196 0 : it->f_timestamp = new_item.f_timestamp;
197 0 : it->f_avg = new_item.f_avg;
198 : }
199 0 : }
200 :
201 :
202 : /** \brief Remove old entries from the list of items.
203 : *
204 : * This function checks each item. If one has a date which is too
205 : * old (i.e. less than now minus \p how_old), then it gets removed
206 : * from the list. The computer may get re-added later.
207 : *
208 : * Assuming everything works as expected, a computer that stops
209 : * sending us the LOADAVG message is considered hanged in some
210 : * way so we do not want to send it any additional work.
211 : *
212 : * In most cases, you want to use the following code to find
213 : * the least busy system to connect to:
214 : *
215 : * \code
216 : * snap::loadavg_file avg;
217 : * avg.load();
218 : * if(avg.remove_old_entries(10))
219 : * {
220 : * avg.save();
221 : * }
222 : * snap::loadavg_item const * item(avg.find_least_busy());
223 : * \endcode
224 : *
225 : * \param[in] how_old The number of seconds after which an entry
226 : * is considered too old to be kept around.
227 : *
228 : * \return true if one or more items were removed.
229 : */
230 0 : bool loadavg_file::remove_old_entries(int how_old)
231 : {
232 0 : size_t const size(f_items.size());
233 0 : time_t const now(time(nullptr) - how_old);
234 0 : f_items.erase(std::remove_if(
235 : f_items.begin(),
236 : f_items.end(),
237 0 : [now](auto const & item)
238 0 : {
239 0 : return item.f_timestamp < now;
240 0 : }),
241 0 : f_items.end());
242 0 : return f_items.size() != size;
243 : }
244 :
245 :
246 : /** \brief Retrieve an entry using its IP address.
247 : *
248 : * This function searches for an item using the specified IP address.
249 : *
250 : * \param[in] addr The address used to search the item.
251 : *
252 : * \return nullptr if no item matched, the pointer of the item if one
253 : * was found with the proper information.
254 : */
255 0 : loadavg_item const * loadavg_file::find(struct sockaddr_in6 const & addr) const
256 : {
257 : auto const & it(std::find_if(
258 : f_items.begin(),
259 : f_items.end(),
260 0 : [addr](auto const & item)
261 : {
262 0 : return item.f_address == addr;
263 0 : }));
264 :
265 0 : if(it == f_items.end())
266 : {
267 0 : return nullptr;
268 : }
269 :
270 0 : return &*it;
271 : }
272 :
273 :
274 : /** \brief Search for least busy server.
275 : *
276 : * This function searches the list of servers and returns the one
277 : * which has the smallest load average amount.
278 : *
279 : * If you want to make sure only fresh data is considered, you
280 : * probably want to call the remove_old_entries() function first.
281 : *
282 : * Note that the function will always return an item if there is
283 : * at least one registered with a mostly current average load.
284 : * If somehow all the servers get removed (too old, unregistered,
285 : * etc.) then the function will return a null pointer.
286 : *
287 : * \return The least busy server or nullptr if no server is available.
288 : *
289 : * \sa remove_old_entries()
290 : */
291 0 : loadavg_item const * loadavg_file::find_least_busy() const
292 : {
293 : auto const & it(std::min_element(
294 : f_items.begin(),
295 : f_items.end(),
296 0 : [](auto const & a, auto const & b)
297 : {
298 0 : return a.f_avg < b.f_avg;
299 0 : }));
300 :
301 0 : if(it == f_items.end())
302 : {
303 0 : return nullptr;
304 : }
305 :
306 0 : return &*it;
307 : }
308 :
309 :
310 6 : } // namespace snap
311 : // vim: ts=4 sw=4 et
|