1 /*-
2  * Copyright (c) 2014-2018 MongoDB, Inc.
3  * Copyright (c) 2008-2014 WiredTiger, Inc.
4  *	All rights reserved.
5  *
6  * See the file LICENSE for redistribution information.
7  */
8 
9 #include "wt_internal.h"
10 
11 #ifdef __GNUC__
12 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)
13 /*
14  * !!!
15  * GCC with -Wformat-nonliteral complains about calls to strftime in this file.
16  * There's nothing wrong, this makes the warning go away.
17  */
18 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
19 #endif
20 #endif
21 
22 /*
23  * __stat_sources_free --
24  *	Free the array of statistics sources.
25  */
26 static void
__stat_sources_free(WT_SESSION_IMPL * session,char *** sources)27 __stat_sources_free(WT_SESSION_IMPL *session, char ***sources)
28 {
29 	char **p;
30 
31 	if ((p = (*sources)) != NULL) {
32 		for (; *p != NULL; ++p)
33 			__wt_free(session, *p);
34 		__wt_free(session, *sources);
35 	}
36 }
37 
38 /*
39  * __stat_config_discard --
40  *	Discard all statistics-log configuration.
41  */
42 static int
__stat_config_discard(WT_SESSION_IMPL * session)43 __stat_config_discard(WT_SESSION_IMPL *session)
44 {
45 	WT_CONNECTION_IMPL *conn;
46 	WT_DECL_RET;
47 
48 	conn = S2C(session);
49 
50 	/*
51 	 * Discard all statistics-log configuration information, called when
52 	 * reconfiguring or destroying the statistics logging setup,
53 	 */
54 	__wt_free(session, conn->stat_format);
55 	ret = __wt_fclose(session, &conn->stat_fs);
56 	__wt_free(session, conn->stat_path);
57 	__stat_sources_free(session, &conn->stat_sources);
58 	conn->stat_stamp = NULL;
59 	conn->stat_usecs = 0;
60 	return (ret);
61 }
62 
63 /*
64  * __wt_conn_stat_init --
65  *	Initialize the per-connection statistics.
66  */
67 void
__wt_conn_stat_init(WT_SESSION_IMPL * session)68 __wt_conn_stat_init(WT_SESSION_IMPL *session)
69 {
70 	WT_CONNECTION_IMPL *conn;
71 	WT_CONNECTION_STATS **stats;
72 
73 	conn = S2C(session);
74 	stats = conn->stats;
75 
76 	__wt_async_stats_update(session);
77 	__wt_cache_stats_update(session);
78 	__wt_las_stats_update(session);
79 	__wt_txn_stats_update(session);
80 
81 	WT_STAT_SET(session, stats, file_open, conn->open_file_count);
82 	WT_STAT_SET(session,
83 	    stats, cursor_open_count, conn->open_cursor_count);
84 	WT_STAT_SET(session, stats, dh_conn_handle_count, conn->dhandle_count);
85 	WT_STAT_SET(session,
86 	    stats, rec_split_stashed_objects, conn->stashed_objects);
87 	WT_STAT_SET(session,
88 	    stats, rec_split_stashed_bytes, conn->stashed_bytes);
89 }
90 
91 /*
92  * __statlog_config --
93  *	Parse and setup the statistics server options.
94  */
95 static int
__statlog_config(WT_SESSION_IMPL * session,const char ** cfg,bool * runp)96 __statlog_config(WT_SESSION_IMPL *session, const char **cfg, bool *runp)
97 {
98 	WT_CONFIG objectconf;
99 	WT_CONFIG_ITEM cval, k, v;
100 	WT_CONNECTION_IMPL *conn;
101 	WT_DECL_ITEM(tmp);
102 	WT_DECL_RET;
103 	int cnt;
104 	char **sources;
105 
106 	/*
107 	 * A note on reconfiguration: the standard "is this configuration string
108 	 * allowed" checks should fail if reconfiguration has invalid strings,
109 	 * for example, "log=(enabled)", or "statistics_log=(path=XXX)", because
110 	 * the connection reconfiguration method doesn't allow those strings.
111 	 * Additionally, the base configuration values during reconfiguration
112 	 * are the currently configured values (so we don't revert to default
113 	 * values when repeatedly reconfiguring), and configuration processing
114 	 * of a currently set value should not change the currently set value.
115 	 *
116 	 * In this code path, a previous statistics log server reconfiguration
117 	 * may have stopped the server (and we're about to restart it). Because
118 	 * stopping the server discarded the configured information stored in
119 	 * the connection structure, we have to re-evaluate all configuration
120 	 * values, reconfiguration can't skip any of them.
121 	 */
122 
123 	conn = S2C(session);
124 	sources = NULL;
125 
126 	/* Only start the server if wait time is non-zero */
127 	WT_RET(__wt_config_gets(session, cfg, "statistics_log.wait", &cval));
128 	*runp = cval.val != 0;
129 	conn->stat_usecs = (uint64_t)cval.val * WT_MILLION;
130 
131 	/*
132 	 * Only set the JSON flag when stats are enabled, otherwise setting
133 	 * this flag can implicitly enable statistics gathering.
134 	 */
135 	WT_RET(__wt_config_gets(session, cfg, "statistics_log.json", &cval));
136 	if (cval.val != 0 && WT_STAT_ENABLED(session))
137 		FLD_SET(conn->stat_flags, WT_STAT_JSON);
138 
139 	WT_RET(__wt_config_gets(
140 	    session, cfg, "statistics_log.on_close", &cval));
141 	if (cval.val != 0)
142 		FLD_SET(conn->stat_flags, WT_STAT_ON_CLOSE);
143 
144 	/*
145 	 * We don't allow the log path to be reconfigured for security reasons.
146 	 * (Applications passing input strings directly to reconfigure would
147 	 * expose themselves to a potential security problem, the utility of
148 	 * reconfiguring a statistics log path isn't worth the security risk.)
149 	 *
150 	 * See above for the details, but during reconfiguration we're loading
151 	 * the path value from the saved configuration information, and it's
152 	 * required during reconfiguration because we potentially stopped and
153 	 * are restarting, the server.
154 	 */
155 	WT_RET(__wt_config_gets(session, cfg, "statistics_log.path", &cval));
156 	WT_ERR(__wt_scr_alloc(session, 0, &tmp));
157 	WT_ERR(__wt_buf_fmt(session,
158 	    tmp, "%.*s/%s", (int)cval.len, cval.str, WT_STATLOG_FILENAME));
159 	WT_ERR(__wt_filename(session, tmp->data, &conn->stat_path));
160 
161 	WT_ERR(__wt_config_gets(session, cfg, "statistics_log.sources", &cval));
162 	__wt_config_subinit(session, &objectconf, &cval);
163 	for (cnt = 0; (ret = __wt_config_next(&objectconf, &k, &v)) == 0; ++cnt)
164 		;
165 	WT_ERR_NOTFOUND_OK(ret);
166 	if (cnt != 0) {
167 		WT_ERR(__wt_calloc_def(session, cnt + 1, &sources));
168 		__wt_config_subinit(session, &objectconf, &cval);
169 		for (cnt = 0;
170 		    (ret = __wt_config_next(&objectconf, &k, &v)) == 0; ++cnt) {
171 			/*
172 			 * XXX
173 			 * Only allow "file:" and "lsm:" for now: "file:" works
174 			 * because it's been converted to data handles, "lsm:"
175 			 * works because we can easily walk the list of open LSM
176 			 * objects, even though it hasn't been converted.
177 			 */
178 			if (!WT_PREFIX_MATCH(k.str, "file:") &&
179 			    !WT_PREFIX_MATCH(k.str, "lsm:"))
180 				WT_ERR_MSG(session, EINVAL,
181 				    "statistics_log sources configuration only "
182 				    "supports objects of type \"file\" or "
183 				    "\"lsm\"");
184 			WT_ERR(
185 			    __wt_strndup(session, k.str, k.len, &sources[cnt]));
186 		}
187 		WT_ERR_NOTFOUND_OK(ret);
188 
189 		conn->stat_sources = sources;
190 		sources = NULL;
191 	}
192 
193 	/*
194 	 * When using JSON format, use the same timestamp format as MongoDB by
195 	 * default. This requires caution: the user might have set the timestamp
196 	 * in a previous reconfigure call and we don't want to override that, so
197 	 * compare the retrieved value with the default value to decide if we
198 	 * should use the JSON default.
199 	 *
200 	 * (This still implies if the user explicitly sets the timestamp to the
201 	 * default value, then sets the JSON flag in a separate reconfigure
202 	 * call, or vice-versa, we will incorrectly switch to the JSON default
203 	 * timestamp. But there's no way to detect that, and this is all a low
204 	 * probability path.)
205 	 *
206 	 * !!!
207 	 * Don't rewrite in the compressed "%FT%T.000Z" form, MSVC13 segfaults.
208 	 */
209 #define	WT_TIMESTAMP_DEFAULT		"%b %d %H:%M:%S"
210 #define	WT_TIMESTAMP_JSON_DEFAULT	"%Y-%m-%dT%H:%M:%S.000Z"
211 	WT_ERR(__wt_config_gets(
212 	    session, cfg, "statistics_log.timestamp", &cval));
213 	if (FLD_ISSET(conn->stat_flags, WT_STAT_JSON) &&
214 	    WT_STRING_MATCH(WT_TIMESTAMP_DEFAULT, cval.str, cval.len))
215 		WT_ERR(__wt_strdup(
216 		    session, WT_TIMESTAMP_JSON_DEFAULT, &conn->stat_format));
217 	else
218 		WT_ERR(__wt_strndup(
219 		    session, cval.str, cval.len, &conn->stat_format));
220 
221 err:	__stat_sources_free(session, &sources);
222 	__wt_scr_free(session, &tmp);
223 
224 	return (ret);
225 }
226 
227 /*
228  * __statlog_print_header --
229  *	Write the header for statistics when running in JSON mode.
230  */
231 static int
__statlog_print_header(WT_SESSION_IMPL * session)232 __statlog_print_header(WT_SESSION_IMPL *session)
233 {
234 	WT_CONNECTION_IMPL *conn;
235 
236 	conn = S2C(session);
237 
238 	if (!FLD_ISSET(conn->stat_flags, WT_STAT_JSON))
239 		return (0);
240 
241 	/*
242 	 * This flag is required in order to generate correct JSON when printing
243 	 * out stats for individual tables. When we are about to print the first
244 	 * table's stats we must print out the wiredTigerTables header once
245 	 * only and add a correct closing brace when we finish the tables
246 	 * section. To do this we maintain a flag variable to note when we have
247 	 * printed the first table. Unfortunately, the mechanism which we use
248 	 * to print stats for each table does not allow passing of variables
249 	 * by reference, this necessitates the use of a variable on the
250 	 * connection. The variable is safe as the JSON printing logic is only
251 	 * performed by the single threaded stat server.
252 	 */
253 	conn->stat_json_tables = false;
254 	WT_RET(__wt_fprintf(session, conn->stat_fs,
255 	    "{\"version\":\"%s\",\"localTime\":\"%s\"",
256 	    WIREDTIGER_VERSION_STRING, conn->stat_stamp));
257 	return (0);
258 }
259 
260 /*
261  * __statlog_print_table_name --
262  *	Write the header for the wiredTigerTables section of statistics if
263  *	running in JSON mode and the header has not been written this round,
264  *	then print the name of the table.
265  */
266 static int
__statlog_print_table_name(WT_SESSION_IMPL * session,const char * name,bool conn_stats)267 __statlog_print_table_name(
268     WT_SESSION_IMPL *session, const char *name, bool conn_stats)
269 {
270 	WT_CONNECTION_IMPL *conn;
271 
272 	conn = S2C(session);
273 
274 	if (!FLD_ISSET(conn->stat_flags, WT_STAT_JSON))
275 		return (0);
276 
277 	/*
278 	 * If printing the connection stats, write that header and we are done.
279 	 */
280 	if (conn_stats) {
281 		WT_RET(__wt_fprintf(
282 		    session, conn->stat_fs, ",\"wiredTiger\":{"));
283 		return (0);
284 	}
285 
286 	/*
287 	 * If this is the first table we are printing stats for print the header
288 	 * for the wiredTigerTables section. Otherwise print a comma as this is
289 	 * a subsequent table.
290 	 */
291 	if (conn->stat_json_tables)
292 		WT_RET(__wt_fprintf(session, conn->stat_fs,","));
293 	else  {
294 		conn->stat_json_tables = true;
295 		WT_RET(__wt_fprintf(session,
296 		    conn->stat_fs,",\"wiredTigerTables\":{"));
297 	}
298 	WT_RET(__wt_fprintf(session, conn->stat_fs, "\"%s\":{", name));
299 	return (0);
300 }
301 
302 /*
303  * __statlog_print_footer --
304  *	Write the footer for statistics when running in JSON mode.
305  */
306 static int
__statlog_print_footer(WT_SESSION_IMPL * session)307 __statlog_print_footer(WT_SESSION_IMPL *session)
308 {
309 	WT_CONNECTION_IMPL *conn;
310 
311 	conn = S2C(session);
312 
313 	if (!FLD_ISSET(conn->stat_flags, WT_STAT_JSON))
314 		return (0);
315 
316 	/* If we have printed a tables stats, then close that section. */
317 	if (conn->stat_json_tables) {
318 		WT_RET(__wt_fprintf(session, conn->stat_fs, "}"));
319 		conn->stat_json_tables = false;
320 	}
321 	WT_RET(__wt_fprintf(session, conn->stat_fs, "}\n"));
322 	return (0);
323 }
324 
325 /*
326  * __statlog_dump --
327  *	Dump out handle/connection statistics.
328  */
329 static int
__statlog_dump(WT_SESSION_IMPL * session,const char * name,bool conn_stats)330 __statlog_dump(WT_SESSION_IMPL *session, const char *name, bool conn_stats)
331 {
332 	WT_CONNECTION_IMPL *conn;
333 	WT_CURSOR *cursor;
334 	WT_DECL_ITEM(tmp);
335 	WT_DECL_RET;
336 	size_t prefixlen;
337 	int64_t val;
338 	const char *desc, *endprefix, *valstr, *uri;
339 	const char *cfg[] = {
340 	    WT_CONFIG_BASE(session, WT_SESSION_open_cursor), NULL };
341 	bool first, groupfirst;
342 
343 	conn = S2C(session);
344 	cursor = NULL;
345 
346 	WT_RET(__wt_scr_alloc(session, 0, &tmp));
347 	first = groupfirst = true;
348 
349 	/* Build URI and configuration string. */
350 	if (conn_stats)
351 		uri = "statistics:";
352 	else {
353 		WT_ERR(__wt_buf_fmt(session, tmp, "statistics:%s", name));
354 		uri = tmp->data;
355 	}
356 
357 	/*
358 	 * Open the statistics cursor and dump the statistics.
359 	 *
360 	 * If we don't find an underlying object, silently ignore it, the object
361 	 * may exist only intermittently.
362 	 */
363 	if ((ret = __wt_curstat_open(session, uri, NULL, cfg, &cursor)) != 0) {
364 		if (ret == EBUSY || ret == ENOENT || ret == WT_NOTFOUND)
365 			ret = 0;
366 		goto err;
367 	}
368 
369 	WT_ERR(__statlog_print_table_name(session, name, conn_stats));
370 	while ((ret = cursor->next(cursor)) == 0) {
371 		WT_ERR(cursor->get_value(cursor, &desc, &valstr, &val));
372 		if (FLD_ISSET(conn->stat_flags, WT_STAT_JSON)) {
373 			/* Check if we are starting a new section. */
374 			endprefix = strchr(desc, ':');
375 			prefixlen = WT_PTRDIFF(endprefix, desc);
376 			WT_ASSERT(session, endprefix != NULL);
377 			if (first ||
378 			    tmp->size != prefixlen ||
379 			    strncmp(desc, tmp->data, tmp->size) != 0) {
380 				WT_ERR(__wt_buf_set(
381 				    session, tmp, desc, prefixlen));
382 				WT_ERR(__wt_fprintf(session, conn->stat_fs,
383 				    "%s\"%.*s\":{", first ? "" : "},",
384 				    (int)prefixlen, desc));
385 				first = false;
386 				groupfirst = true;
387 			}
388 			WT_ERR(__wt_fprintf(session, conn->stat_fs,
389 			    "%s\"%s\":%" PRId64,
390 			    groupfirst ? "" : ",", endprefix + 2, val));
391 			groupfirst = false;
392 		} else
393 			WT_ERR(__wt_fprintf(session, conn->stat_fs,
394 			    "%s %" PRId64 " %s %s\n",
395 			    conn->stat_stamp, val, name, desc));
396 	}
397 	WT_ERR_NOTFOUND_OK(ret);
398 	if (FLD_ISSET(conn->stat_flags, WT_STAT_JSON))
399 		WT_ERR(__wt_fprintf(session, conn->stat_fs, "}}"));
400 
401 err:	__wt_scr_free(session, &tmp);
402 	if (cursor != NULL)
403 		WT_TRET(cursor->close(cursor));
404 	return (ret);
405 }
406 
407 /*
408  * __statlog_apply --
409  *	Review a single open handle and dump statistics on demand.
410  */
411 static int
__statlog_apply(WT_SESSION_IMPL * session,const char * cfg[])412 __statlog_apply(WT_SESSION_IMPL *session, const char *cfg[])
413 {
414 	WT_DATA_HANDLE *dhandle;
415 	WT_DECL_RET;
416 	char **p;
417 
418 	WT_UNUSED(cfg);
419 
420 	dhandle = session->dhandle;
421 
422 	/* Check for a match on the set of sources. */
423 	for (p = S2C(session)->stat_sources; *p != NULL; ++p)
424 		if (WT_PREFIX_MATCH(dhandle->name, *p)) {
425 			WT_WITHOUT_DHANDLE(session, ret =
426 			    __statlog_dump(session, dhandle->name, false));
427 			return (ret);
428 		}
429 	return (0);
430 }
431 
432 /*
433  * __statlog_lsm_apply --
434  *	Review the list open LSM trees, and dump statistics on demand.
435  *
436  * XXX
437  * This code should be removed when LSM objects are converted to data handles.
438  */
439 static int
__statlog_lsm_apply(WT_SESSION_IMPL * session)440 __statlog_lsm_apply(WT_SESSION_IMPL *session)
441 {
442 #define	WT_LSM_TREE_LIST_SLOTS	100
443 	WT_LSM_TREE *lsm_tree, *list[WT_LSM_TREE_LIST_SLOTS];
444 	WT_DECL_RET;
445 	int cnt;
446 	bool locked;
447 	char **p;
448 
449 	cnt = locked = 0;
450 
451 	/*
452 	 * Walk the list of LSM trees, checking for a match on the set of
453 	 * sources.
454 	 *
455 	 * XXX
456 	 * We can't hold the schema lock for the traversal because the LSM
457 	 * statistics code acquires the tree lock, and the LSM cursor code
458 	 * acquires the tree lock and then acquires the schema lock, it's a
459 	 * classic deadlock.  This is temporary code so I'm not going to do
460 	 * anything fancy.
461 	 * It is OK to not keep holding the schema lock after populating
462 	 * the list of matching LSM trees, since the __wt_lsm_tree_get call
463 	 * will bump a reference count, so the tree won't go away.
464 	 */
465 	__wt_spin_lock(session, &S2C(session)->schema_lock);
466 	locked = true;
467 	TAILQ_FOREACH(lsm_tree, &S2C(session)->lsmqh, q) {
468 		if (cnt == WT_LSM_TREE_LIST_SLOTS)
469 			break;
470 		for (p = S2C(session)->stat_sources; *p != NULL; ++p)
471 			if (WT_PREFIX_MATCH(lsm_tree->name, *p)) {
472 				WT_ERR(__wt_lsm_tree_get(session,
473 				    lsm_tree->name, false, &list[cnt++]));
474 				break;
475 			}
476 	}
477 	__wt_spin_unlock(session, &S2C(session)->schema_lock);
478 	locked = false;
479 
480 	while (cnt > 0) {
481 		--cnt;
482 		WT_TRET(__statlog_dump(session, list[cnt]->name, false));
483 		__wt_lsm_tree_release(session, list[cnt]);
484 	}
485 
486 err:	if (locked)
487 		__wt_spin_unlock(session, &S2C(session)->schema_lock);
488 	/* Release any LSM trees on error. */
489 	while (cnt > 0) {
490 		--cnt;
491 		__wt_lsm_tree_release(session, list[cnt]);
492 	}
493 	return (ret);
494 }
495 
496 /*
497  * __statlog_log_one --
498  *	Output a set of statistics into the current log file.
499  */
500 static int
__statlog_log_one(WT_SESSION_IMPL * session,WT_ITEM * path,WT_ITEM * tmp)501 __statlog_log_one(WT_SESSION_IMPL *session, WT_ITEM *path, WT_ITEM *tmp)
502 {
503 	struct timespec ts;
504 	struct tm localt;
505 	WT_CONNECTION_IMPL *conn;
506 
507 	conn = S2C(session);
508 
509 	/* Get the current local time of day. */
510 	__wt_epoch(session, &ts);
511 	WT_RET(__wt_localtime(session, &ts.tv_sec, &localt));
512 
513 	/* Create the logging path name for this time of day. */
514 	if (strftime(tmp->mem, tmp->memsize, conn->stat_path, &localt) == 0)
515 		WT_RET_MSG(session, ENOMEM, "strftime path conversion");
516 
517 	/* If the path has changed, cycle the log file. */
518 	if (conn->stat_fs == NULL ||
519 	    path == NULL || strcmp(tmp->mem, path->mem) != 0) {
520 		WT_RET(__wt_fclose(session, &conn->stat_fs));
521 		WT_RET(__wt_fopen(session, tmp->mem,
522 		    WT_FS_OPEN_CREATE | WT_FS_OPEN_FIXED, WT_STREAM_APPEND,
523 		    &conn->stat_fs));
524 
525 		if (path != NULL)
526 			WT_RET(__wt_buf_setstr(session, path, tmp->mem));
527 	}
528 
529 	/* Create the entry prefix for this time of day. */
530 	if (strftime(tmp->mem, tmp->memsize, conn->stat_format, &localt) == 0)
531 		WT_RET_MSG(session, ENOMEM, "strftime timestamp conversion");
532 	conn->stat_stamp = tmp->mem;
533 	WT_RET(__statlog_print_header(session));
534 
535 	/* Dump the connection statistics. */
536 	WT_RET(__statlog_dump(session, conn->home, true));
537 
538 	/*
539 	 * Lock the schema and walk the list of open handles, dumping
540 	 * any that match the list of object sources.
541 	 */
542 	if (conn->stat_sources != NULL)
543 		WT_RET(__wt_conn_btree_apply(
544 		    session, NULL, __statlog_apply, NULL, NULL));
545 
546 	/*
547 	 * Walk the list of open LSM trees, dumping any that match the
548 	 * the list of object sources.
549 	 *
550 	 * XXX
551 	 * This code should be removed when LSM objects are converted to
552 	 * data handles.
553 	 */
554 	if (conn->stat_sources != NULL)
555 		WT_RET(__statlog_lsm_apply(session));
556 	WT_RET(__statlog_print_footer(session));
557 
558 	/* Flush. */
559 	return (__wt_fflush(session, conn->stat_fs));
560 }
561 
562 /*
563  * __statlog_on_close --
564  *	Log a set of statistics at close. Requires the server is not currently
565  * running.
566  */
567 static int
__statlog_on_close(WT_SESSION_IMPL * session)568 __statlog_on_close(WT_SESSION_IMPL *session)
569 {
570 	WT_CONNECTION_IMPL *conn;
571 	WT_DECL_ITEM(tmp);
572 	WT_DECL_RET;
573 
574 	conn = S2C(session);
575 
576 	if (!FLD_ISSET(conn->stat_flags, WT_STAT_ON_CLOSE))
577 		return (0);
578 
579 	if (F_ISSET(conn, WT_CONN_SERVER_STATISTICS))
580 		WT_RET_MSG(session, EINVAL,
581 		    "Attempt to log statistics while a server is running");
582 
583 	WT_RET(__wt_scr_alloc(session, strlen(conn->stat_path) + 128, &tmp));
584 	WT_ERR(__wt_buf_setstr(session, tmp, ""));
585 	WT_ERR(__statlog_log_one(session, NULL, tmp));
586 
587 err:	__wt_scr_free(session, &tmp);
588 	return (ret);
589 }
590 
591 /*
592  * __statlog_server_run_chk --
593  *	Check to decide if the statistics log server should continue running.
594  */
595 static bool
__statlog_server_run_chk(WT_SESSION_IMPL * session)596 __statlog_server_run_chk(WT_SESSION_IMPL *session)
597 {
598 	return (F_ISSET(S2C(session), WT_CONN_SERVER_STATISTICS));
599 }
600 
601 /*
602  * __statlog_server --
603  *	The statistics server thread.
604  */
605 static WT_THREAD_RET
__statlog_server(void * arg)606 __statlog_server(void *arg)
607 {
608 	WT_CONNECTION_IMPL *conn;
609 	WT_DECL_RET;
610 	WT_ITEM path, tmp;
611 	WT_SESSION_IMPL *session;
612 
613 	session = arg;
614 	conn = S2C(session);
615 
616 	/*
617 	 * We need a temporary place to build a path and an entry prefix.
618 	 * The length of the path plus 128 should be more than enough.
619 	 *
620 	 * We also need a place to store the current path, because that's
621 	 * how we know when to close/re-open the file.
622 	 */
623 	WT_CLEAR(path);
624 	WT_ERR(__wt_buf_init(session, &path, strlen(conn->stat_path) + 128));
625 	WT_ERR(__wt_buf_setstr(session, &path, ""));
626 	WT_CLEAR(tmp);
627 	WT_ERR(__wt_buf_init(session, &tmp, strlen(conn->stat_path) + 128));
628 	WT_ERR(__wt_buf_setstr(session, &tmp, ""));
629 
630 	for (;;) {
631 		/* Wait until the next event. */
632 		__wt_cond_wait(session, conn->stat_cond,
633 		    conn->stat_usecs, __statlog_server_run_chk);
634 
635 		/* Check if we're quitting or being reconfigured. */
636 		if (!__statlog_server_run_chk(session))
637 			break;
638 
639 		if (WT_STAT_ENABLED(session))
640 			WT_ERR(__statlog_log_one(session, &path, &tmp));
641 	}
642 
643 	if (0) {
644 err:		WT_PANIC_MSG(session, ret, "statistics log server error");
645 	}
646 	__wt_buf_free(session, &path);
647 	__wt_buf_free(session, &tmp);
648 	return (WT_THREAD_RET_VALUE);
649 }
650 
651 /*
652  * __statlog_start --
653  *	Start the statistics server thread.
654  */
655 static int
__statlog_start(WT_CONNECTION_IMPL * conn)656 __statlog_start(WT_CONNECTION_IMPL *conn)
657 {
658 	WT_SESSION_IMPL *session;
659 
660 	/* Nothing to do if the server is already running. */
661 	if (conn->stat_session != NULL)
662 		return (0);
663 
664 	F_SET(conn, WT_CONN_SERVER_STATISTICS);
665 
666 	/* The statistics log server gets its own session. */
667 	WT_RET(__wt_open_internal_session(
668 	    conn, "statlog-server", true, 0, &conn->stat_session));
669 	session = conn->stat_session;
670 
671 	WT_RET(__wt_cond_alloc(
672 	    session, "statistics log server", &conn->stat_cond));
673 
674 	/*
675 	 * Start the thread.
676 	 *
677 	 * Statistics logging creates a thread per database, rather than using
678 	 * a single thread to do logging for all of the databases. If we ever
679 	 * see lots of databases at a time, doing statistics logging, and we
680 	 * want to reduce the number of threads, there's no reason we have to
681 	 * have more than one thread, I just didn't feel like writing the code
682 	 * to figure out the scheduling.
683 	 */
684 	WT_RET(__wt_thread_create(
685 	    session, &conn->stat_tid, __statlog_server, session));
686 	conn->stat_tid_set = true;
687 
688 	return (0);
689 }
690 
691 /*
692  * __wt_statlog_create --
693  *	Start the statistics server thread.
694  */
695 int
__wt_statlog_create(WT_SESSION_IMPL * session,const char * cfg[])696 __wt_statlog_create(WT_SESSION_IMPL *session, const char *cfg[])
697 {
698 	WT_CONNECTION_IMPL *conn;
699 	bool start;
700 
701 	conn = S2C(session);
702 
703 	/*
704 	 * Stop any server that is already running. This means that each time
705 	 * reconfigure is called we'll bounce the server even if there are no
706 	 * configuration changes. This makes our life easier as the underlying
707 	 * configuration routine doesn't have to worry about freeing objects
708 	 * in the connection structure (it's guaranteed to always start with a
709 	 * blank slate), and we don't have to worry about races where a running
710 	 * server is reading configuration information that we're updating, and
711 	 * it's not expected that reconfiguration will happen a lot.
712 	 *
713 	 * If there's no server running, discard any configuration information
714 	 * so we don't leak memory during reconfiguration.
715 	 */
716 	if (conn->stat_session == NULL)
717 		WT_RET(__stat_config_discard(session));
718 	else
719 		WT_RET(__wt_statlog_destroy(session, false));
720 
721 	WT_RET(__statlog_config(session, cfg, &start));
722 	if (start)
723 		WT_RET(__statlog_start(conn));
724 
725 	return (0);
726 }
727 
728 /*
729  * __wt_statlog_destroy --
730  *	Destroy the statistics server thread.
731  */
732 int
__wt_statlog_destroy(WT_SESSION_IMPL * session,bool is_close)733 __wt_statlog_destroy(WT_SESSION_IMPL *session, bool is_close)
734 {
735 	WT_CONNECTION_IMPL *conn;
736 	WT_DECL_RET;
737 	WT_SESSION *wt_session;
738 
739 	conn = S2C(session);
740 
741 	/* Stop the server thread. */
742 	F_CLR(conn, WT_CONN_SERVER_STATISTICS);
743 	if (conn->stat_tid_set) {
744 		__wt_cond_signal(session, conn->stat_cond);
745 		WT_TRET(__wt_thread_join(session, &conn->stat_tid));
746 		conn->stat_tid_set = false;
747 	}
748 	__wt_cond_destroy(session, &conn->stat_cond);
749 
750 	/* Log a set of statistics on shutdown if configured. */
751 	if (is_close)
752 		WT_TRET(__statlog_on_close(session));
753 
754 	/* Discard all configuration information. */
755 	WT_TRET(__stat_config_discard(session));
756 
757 	/* Close the server thread's session. */
758 	if (conn->stat_session != NULL) {
759 		wt_session = &conn->stat_session->iface;
760 		WT_TRET(wt_session->close(wt_session, NULL));
761 		conn->stat_session = NULL;
762 	}
763 
764 	return (ret);
765 }
766