It implements the internal and build-in runtime statistics collection.
You can examine it manually with 'statistics' command or save regularly to
external databases, i.e. CSV files or Graphite application.
The very detailed description of the all functionalities implemented can be
found in README.Collector.txt file.
# win32
/bacula/src/win32/lib/bacula32.def
/bacula/src/win32/lib/bacula64.def
+
+# OS and Editor/IDE files
+.AppleDouble
--- /dev/null
+
+This is a brief description of a new Statistics collector functionality.
+
+0. Requirements and Assumptions
+
+>>>Eric Bollengier <eric.bollengier@baculasystems.com> at 12/04/2018
+>>>
+The main idea would be to generate statistics from our daemon (mainly the
+storage daemon, but FD and Dir can be interested too) and send this data to a
+Graphite daemon.
+
+Graphite is a bit like RRDTool, it is in charge to collect the data and render
+the information.
+
+https://graphiteapp.org/
+
+I think that the solution at the Bacula level should be able to use different
+"drivers" (csv file on disk with a configurable format for example, native
+graphite tcp connection, etc...).
+
+At the Bacula level, we probably need a new Resource in the configuration file
+to configure that (or only new directives I don't know). Once you know what you
+need, Kern will review the name of the Resources and/or Directives.
+
+Each job should be able to send data to the Bacula collector (ie from multiple
+threads). Ideally, a job should not be blocked because the Graphite socket is
+hanging for example.
+
+We need to define interesting metrics that can be reported to the statistic
+collector, few examples:
+- nb of jobs at a given time
+- device statistics (nb read, nb write, nb job...)
+- total network throughput
+- per job network throughput
+- disk throughput
+- current memory usage
+- system information (cpu, load average, swap usage)
+- number of files read for a job
+- (basically what the status command reports)
+
+It might be interesting to let the user choose the metrics they want to see, and
+have a directive such as the destination message.
+
+ Metrics = NbJob, NetworkThroughput, DiskThroughput, MemoryUsage
+
+(this is just an idea).
+
+We can start with few basic statistics and enrich the program later.
+
+1. Statistics Collector Architecture
+
+The most important requirement is to not block a job thread because external
+communication stalled or introduced unexpected latency which can negatively
+affect a running job. This requirement lead to the strict separation in
+Collector functionality and behavior.
+
+The collector was designed as a two separate entities:
+- an internal collector class: bstatcollect
+- an interface collector thread: COLLECTOR
+The first one functions as a metrics cache and the second one is responsible for
+sending collected metrics into an external data target (i.e. Graphite).
+
+1.1. Statistics Collection flow
+
+Any Bacula's metrics are collected in a push architecture where an object code
+is responsible for preparing/generating a metrics and "push" it to internal
+collector for later usage. This "push" operation should be extremely fast which
+allows to avoid unexpected latency in the job.
+
+To save a metrics in internal collector requires two step process: metric
+registration and metrics update. Metrics registration could take some time
+(relatively speaking) with O(n) and return a metrics index used in metrics update
+process which is very fast with O(1).
+
+You should register metrics at the very beginning of the job or daemon start when
+additional latency is not a problem at all. The update process using the metric
+index from registration will be very fast, so it should be no problem setting it
+even at the time critical part of the code, i.e.
+
+metric_index = statcollector->registration_int64("bacula.jobs.all",
+ METRIC_UNIT_JOB, value, "The number of all jobs");
+
+(...)
+
+while (true)
+
+(...)
+
+ statcollector->set_value_int64(metric_index, newvalue);
+
+(...)
+
+statcollector->unregistration(metric_index);
+
+The only latency introduced by a metrics update process is lock/unlock resolution
+used for internal collector synchronization access.
+
+The metrics should be unregistered when no needed any more. After unregistration
+process the metrics index becomes invalid and should not be used to address this
+metric. As the metrics index is a regular integer number it will be reused upon
+next registration of any new metric.
+
+You can get any or all metrics values when required. The return will be always
+the full copy of the metrics so you can process it as you wish.
+
+1.2. Statistics Collector backend thread
+
+The collector background thread (COLLECTOR resource) is responsible for getting
+the copy of the current metrics list and save them to configured destination.
+The save/send process could be a time consuming, i.e. could involve a network
+communication like for a Graphite collector. As this collector thread operates
+on a copy of metrics list it doesn't affect standard job operations. Collector
+thread saves metrics at regular intervals.
+ Note: the current implementation introduced a two backends: CSV file and
+ Graphite which are build in backends.
+
+1.3. Statistics Collector update thread
+
+We have a two metrics types to collect: an easy to count/update and a hard one
+to count/update. The easy to count/update metrics are all statistics which
+corresponds to already available (in memory) counter/variable, so we can update
+metrics every time the counter/variable is updated. We can achieve a perfect
+accuracy here without a problem. On the other hand the hard to count/update
+metrics are all metrics which depends on external data (i.e. almost all
+permanent metrics based on catalog data), metrics which are not directly
+controllable by Bacula (i.e. the size of the heap) or metrics where frequent
+update will bring a huge performance impact (i.e. sm_pool_memory size). For this
+kind of metrics we've develop a dedicated mechanism for updating these metrics.
+
+The main assumption here is that these metrics won't be updated until necessary,
+so as long as nobody would check what is the value we won't update it. We agreed
+that the real value of the metrics (i.e. a number of error jobs) could change a
+dozens of times in the mean time, but we want a perfect value at the time of
+sampling, so i.e. saving/sending to external backend.
+
+For this purpose we run a dedicated collector update thread which will start
+only when any of collector backend threads are started. So, if no collectors
+defined for the daemon, no update thread will be necessary. The collector update
+thread executes a dedicated function to every daemon as every daemon would has a
+different set of hard to count/update metrics. The collector update thread
+updates required metrics as frequent as the minimal interval parameter set at
+defined collector resources. So, for two collector resources which would have
+Interval=5min and Interval=30sec, the update thread should get a 30secs
+interval. Additionally the "collect" command, which display all available
+metrics at any time, is executing the same update function as for update thread
+to get up to date metrics.
+
+2. Architecture implementation and code
+
+2.1. The metrics class
+
+This is a basic building block for metrics collection:
+
+class bstatmetrics : public SMARTALLOC {
+public:
+ char *name; /* this is a metrics name */
+ metric_type_t type; /* this is a metrics type */
+ metric_unit_t unit; /* this is a metrics unit */
+ metric_value_t value; /* this is a metrics value */
+ char *description; /* this is a metrics description */
+ bstatmetric();
+ bstatmetric(char *mname, metric_type_t mtype, metric_unit_t munit,
+ char *descr);
+ bstatmetric(char *mname, metric_unit_t munit, bool mvalue, char *descr);
+ bstatmetric(char *mname, metric_unit_t munit, int64_t mvalue, char *descr);
+ bstatmetric(char *mname, metric_unit_t munit, float mvalue, char *descr);
+ ~bstatmetric();
+ bstatmetric& operator=(const bstatmetric& orig);
+ void render_metric_value(POOLMEM **buf, bool bstr=false);
+ void render_metric_value(POOL_MEM &buf, bool bstr=false);
+ const char *metric_type_str();
+ const char *metric_unit_str();
+ void dump();
+};
+
+You can have a three (technically four) types of metrics in bstatmetric:
+- METRIC_UNDEF - when bstatmetrics is uninitialized
+- METRIC_INT - the metrics stores an integer values (int64_t)
+- METRIC_BOOL - the metrics stores a boolean values (so, True/False)
+- METRIC_FLOAT - the metrics stores a float values
+
+You can define a metrics unit, which shows what entity a value represent, i.e.
+METRIC_UNIT_BYTE, METRIC_UNIT_BYTESEC, METRIC_UNIT_JOB, METRIC_UNIT_CLIENT, etc.
+When a value have no unit and it is a just a value/number you should use:
+METRIC_UNIT_EMPTY.
+
+2.2. The internal collector class
+
+The metrics objects collection is an internal collector class:
+
+class bstatcollect : public SMARTALLOC {
+public:
+ bstatcollect();
+ /* registration return a metrics index */
+ int registration(char *metric, metric_type_t type, metric_unit_t unit,
+ char *descr);
+ int registration_bool(char *metric, metric_unit_t unit, bool value,
+ char *descr);
+ int registration_int64(char *metric, metric_unit_t unit, int64_t value,
+ char *descr);
+ int registration_float(char *metric, metric_unit_t unit, float value,
+ char *descr);
+ /* unregistration */
+ void unregistration(int metric);
+ /* update/set the metrics value */
+ int set_value_bool(int metric, bool value);
+ int set_value_int64(int metric, int64_t value);
+ int add_value_int64(int metric, int64_t value);
+ int add2_value_int64(int metric1, int64_t value1, int metric2,
+ int64_t value2);
+ int sub_value_int64(int metric, int64_t value);
+ int set_value_float(int metric, float value);
+ int inc_value_int64(int metric);
+ int dec_value_int64(int metric);
+ int dec_inc_values_int64(int metricd, int metrici);
+ /* get data */
+ bool get_bool(int metric);
+ int64_t get_int(int metric);
+ float get_float(int metric);
+ alist *get_all();
+ bstatmetrics *get_metric(char *metric);
+ /* utility */
+ void dump();
+};
+
+You can register a metrics of particular type and unit with
+bstatcollect::registration() method. In this case the value will be set
+initially to zero. Using other registration_*() methods you can set other
+initial value.
+
+When you make a next metrics registration in the time when a particular metrics
+already exist in bstatcollect object you will always get the same metrics index.
+When you unregister a metrics and register it later again you could get a
+different metrics index.
+
+Any metrics could have a description string which could be useful to users. You
+can set metrics description at first registration only. Any subsequent metrics
+registration does not update it.
+
+2.2.1. Updating the metrics value
+
+Any metrics value update should be performed as an atomic operation, so the
+internal collector class has a number of a such methods:
+- set_value_*() - will set the metrics value into method argument "value", old
+ metrics value will be overwritten
+- inc_value_int64()/dec_value_int64() - will increment or decrement the metrics
+ value accordingly as a single atomic operation
+- dec_inc_values_int64() - will decrement a first metrics value and increment
+ a second metrics value as a single atomic operation, used to update a related
+ metrics in a single step
+- add_value_int64() - will add the numeric argument to the metrics value as a
+ single atomic operation
+- add2_value_int64() - will add the two numeric arguments to the two metrics
+ values as a single atomic operation, used to update more metrics in a single
+ step
+
+The inc_value_int64()/dec_value_int64()/add_value_int64()/etc. should be used
+when managing a "shared" metrics updated from a different or separate threads.
+
+2.3. Supported utilities
+
+There are a few supported utilities you can use when processing metrics or
+metrics list:
+- bstatmetric::render_metric_value() - render a metrics value as a string into
+ a buffer.
+- bstatmetric::metric_type_str() - return a metrics type as string.
+- bstatmetric::metric_unit_str() - return a metrics unit as string.
+- free_metric_alist() - releases memory for a list of metrics returned from
+ bstatcollect::get_all().
+
+3. Statistics resource configuration
+
+The Statistics resource defines the attributes of the Statistics collector threads
+running on any daemon. You can define any number of Collector resources and every
+single Statistics resource will spawn a single collector thread. This resource can be
+defined for any Daemon (Dir, SD and FD). Resource directives:
+
+Name = <name> The collector name used by the system administrator. This
+directive is required.
+
+Description = <text> The text field contains a description of the Collector that
+will be displayed in the graphical user interface. This directive is optional.
+
+Interval = <time-interval> This directive instruct Collector thread how long it
+should sleep between every collection iteration. This directive is optional and
+when not specified a value 300 seconds will be used instead.
+
+Type = <backend> The Type directive specifies the Collector backend, which may
+be one of the following: CSV or Graphite. This directive is required.
+
+-> CSV is a simple file level backend which saves all required metrics with the
+following format to the file: <time>, <metric>, <value>\n
+
+--> where: <time> is a standard Unix time (a number of seconds from 1/01/1970)
+with local timezone as returned by a system call time(). <metric> is a Bacula
+metrics string and <vale> is a metrics value which could be in numeric format
+(int/float) or a string "True" or "False" for boolean variable. The CSV backend
+require a File=... parameter.
+
+-> Graphite is a network backend which will send all required metrics to
+Graphite server. This backend requires Host=... and Port=... directives to be
+set.
+
+Metrics = <metricspec> This directive allow metrics filtering and <metricspec> is
+a filter which uses "*" and "?" characters to match required metrics name in the
+same way as found in shell wildcard resolution. You can exclude filtered metrics
+with '!' prefix. You can define any number of filters for a single Collector.
+Metrics filter is executed in order as found in configuration. This directive is
+optional and if not used all available metrics will be saved by this collector
+backend. Example:
+- include any metrics started with bacula.jobs:
+ Metrics = "bacula.jobs.*"
+- exclude any metrics started with bacula.jobs
+ Metrics = "!bacula.jobs.*"
+
+Prefix = <prefix> This directive allows to alter the metrics name saved by
+collector to distinguish between different installations or daemons. The
+<prefix> string will be added to metrics name as: <prefix>.<metric_name>
+This directive is optional. If not set no metrics name will be altered.
+
+File = <filename> This directive is used for CSV collector backend only and
+point to the full path and filename of the file where metrics will be saved.
+In this case the directive is required. The collector thread has to have a
+permissions to write to selected file or create a new file if it won't exist.
+If collector will be unable to write to the file or create a new one then
+a collection terminate and error message will be generated. The file is open
+during saving only and closed otherwise. Statistics file rotation could be
+executed by a simple mv ... shell command.
+
+Host = <IP-Address> This directive is used for Graphite backend only and
+specify the IP address or hostname of the Graphite server. In this case the
+directive is required.
+
+Port = <port-number> This directive is used for Graphite backend only and
+specify the port number for Graphite server connection. In this case the
+directive is required.
+
+MangleMetric = <yes|no> This directive will replace all dots in your resource
+metric with '%32'. It is especially useful when using Graphite collector
+backend and Bacula's resource names with dots the Graphite application will
+incorrectly split metrics tree based on resource name. It could be useful if
+user wants another level of metrics separation but it could be annoying when
+used unintentionally. This string could be demangled in GUI - i.e. BWeb.
+
+The currently developed are a two Collector backends: CSV and Graphite. Other
+backends could be developed if required. The Statistics resource is a build-in
+feature.
+
+3.1. Metrics spooling
+
+Metrics spooling is a feature build in some backends which allows to postpone
+metrics delivery to external database (i.e. Graphite) in case of any delivery
+problems. The postpone delivery uses regular file located in a Working Directory
+of the daemon as a spool file. The metrics spooling to file starts automatically
+and run until delivery destination will become available again. In this case
+spooled metrics will be despooled automatically and spooling function suspend
+until the next delivery problem. This feature is available now for Graphite
+backend only. When metrics spooling cannot be performed (i.e. cannot create the
+spooling file) the spooling will be disabled until daemon restart or
+configuration reload. The spooling file is not deleted on daemon restart, so any
+spooled metrics will be saved until destination become available (despooling).
+
+4. Bconsole
+
+You can manually display Bacula metrics with bconsole command: collect.
+
+statistics all | <metric> [ simple | full | json ] This command display all Bacula
+metrics when all keyword is set or a single particular metrics with <metric>
+parameter. You can change the output format with simple, full or json keywords.
+When no format keyword is set then a simple format will be used.
+
+- Simple format example:
+
+*statistics bacula.jobs.all
+bacula.jobs.all=276
+
+- Full format example:
+
+*statistics bacula.jobs.all full
+name="bacula.jobs.all" value=276 type=Integer unit=Jobs descr="Number of all
+jobs."
+
+- JSON format example:
+
+*statistics bacula.jobs.all json
+[
+ {
+ "name": "bacula.jobs.all",
+ "value": 164,
+ "type": "Integer",
+ "unit": "Jobs",
+ "description": "Number of all jobs."
+ }
+]
+
+By default you will query a Director daemon with statistics command. If you wish to
+query a Storage Daemon or File Daemon then you have to add a storage=<sd> or
+client=<fd> parameters to your statistics command, i.e.
+
+*statistics bacula.storage.File1.config.devices storage=File1
+Connecting to Storage daemon File1 at bacula-devel:9103
+bacula.storage.File1.config.devices=4
+
+*statistics bacula.storage.bacula-devel-sd.config.devices storage=File1 json
+Connecting to Storage daemon File1 at 192.168.0.224:9103
+[
+ {
+ "name": "bacula.storage.bacula-devel-sd.config.devices",
+ "value": 4,
+ "type": "Integer",
+ "unit": "Device",
+ "description": "The number of defined devices in Storage."
+ }
+]
+
+This feature does not require any Statistics backend resource to be setup.
+
+You can display any Statistics resource configuration using the bconsole command:
+show collector, i.e.
+
+*show statistics
+Statistics: name=CSV collector
+ type=1 interval=300 secs
+ prefix=
+ file=/tmp/stat-dir.csv
+Statistics: name=Graphite
+ type=2 interval=60 secs
+ prefix=backup
+ host=192.168.0.229 port=2003
+
+You can display a status of any running Statistics backend with a special
+".status <daemon> statistics[=<name>]" command. You have to give a proper daemon
+resource name, i.e. "dir", "storage[=<name>]", "client[=<name>]" to point which
+daemon you want to examine. You can limit a ".status" command to display
+information about a single Statistics backend with "statistics=<name>" parameter.
+When no statistics name will be set then information about all Statistics backends
+and Statistics update thread will be displayed, i.e.
+
+*.status dir statistics
+Statistics backend: CSV collector is running
+ type=1 lasttimestamp=24-Jul-18 10:51
+ interval=300 secs
+ spooling=disabled
+
+Statistics backend: Graphite is running
+ type=2 lasttimestamp=24-Jul-18 10:51
+ interval=60 secs
+ spooling=enabled
+
+Update Statistics: running interval=60 secs lastupdate=24-Jul-18 10:51
+
+*.status client=bacula-devel-fd statistics=Graphite
+Connecting to Client bacula-devel-fd at bacula-devel:9102
+Statistics backend: Graphite is running
+ type=2 lasttimestamp=24-Jul-18 10:52
+ interval=30 secs
+ spooling=enabled
+
+A ".status statistics" command support APIv2 display, i.e.
+
+*.api 2
+*.status dir statistics
+collector_backends:
+
+name=CSV1
+status=running
+interval=60
+lasttimestamp_epoch=1561643144
+lasttimestamp=2019-06-27 15:45:44
+spooling=disabled
+lasterror=
+
+
+name=CSV2
+status=running
+interval=60
+lasttimestamp_epoch=1561643144
+lasttimestamp=2019-06-27 15:45:44
+spooling=disabled
+lasterror=
+
+
+name=Graphite1
+status=running
+interval=60
+lasttimestamp_epoch=1561643147
+lasttimestamp=2019-06-27 15:45:47
+spooling=in progress
+lasterror=Could not connect to localhost:9223 Err=Connection refused
+
+
+collector_update:
+
+status=running
+interval=60
+lasttimestamp_epoch=1561643144
+lasttimestamp=2019-06-27 15:45:44
"DELETE FROM %s WHERE FileId IN (SELECT FileId FROM %s JOIN File USING (FileId) WHERE PathId IN (%s))"
};
+const char *count_all_jobs = "SELECT COUNT(1) FROM Job";
+const char *count_success_jobs = "SELECT COUNT(1) FROM Job WHERE JobStatus IN ('T', 'I') AND JobErrors=0";
+const char *count_success_jobids = "SELECT COUNT(1) FROM Job WHERE JobStatus IN ('T', 'I') AND JobErrors=0 and JobId in (%s)";
+const char *count_error_jobs = "SELECT COUNT(1) FROM Job WHERE JobStatus IN ('E','f','A')";
+const char *count_error_jobids = "SELECT COUNT(1) FROM Job WHERE JobStatus IN ('E','f','A') and JobId in (%s)";
+const char *count_warning_jobs = "SELECT COUNT(1) FROM Job WHERE JobStatus IN ('T','I') AND NOT JobErrors=0";
+const char *count_warning_jobids = "SELECT COUNT(1) FROM Job WHERE JobStatus IN ('T','I') AND NOT JobErrors=0 and JobId in (%s)";
+const char *sum_jobs_bytes = "SELECT SUM(JobBytes) FROM Job";
+const char *sum_jobids_bytes = "SELECT SUM(JobBytes) FROM Job WHERE JobId in (%s)";
+const char *sum_jobs_files = "SELECT SUM(JobFiles) FROM Job";
+const char *sum_jobids_files = "SELECT SUM(JobFiles) FROM Job WHERE JobId in (%s)";
+const char *count_all_volumes = "SELECT COUNT(1) FROM Media";
+const char *count_avl_volumes = "SELECT COUNT(1) FROM Media WHERE VolStatus='Append'";
+const char *count_error_volumes = "SELECT COUNT(1) FROM Media WHERE VolStatus='Error'";
+const char *count_full_volumes = "SELECT COUNT(1) FROM Media WHERE VolStatus='Full'";
+const char *count_used_volumes = "SELECT COUNT(1) FROM Media WHERE VolStatus='Used'";
+const char *sum_volumes_bytes = "SELECT SUM(VolBytes) FROM Media";
+const char *get_volume_size = "SELECT VolBytes FROM Media WHERE VolumeName='%s'";
static const char *escape_char_value_default = "\\";
const char *escape_char_value[] = {
escape_char_value_default,
/* SQLite */
escape_char_value_default
-};
\ No newline at end of file
+};
extern const char CATS_IMP_EXP *expires_in[];
extern const char CATS_IMP_EXP *prune_cache[];
extern const char CATS_IMP_EXP *strip_restore[];
+extern const char CATS_IMP_EXP *count_all_jobs;
+extern const char CATS_IMP_EXP *count_success_jobs;
+extern const char CATS_IMP_EXP *count_success_jobids;
+extern const char CATS_IMP_EXP *count_error_jobs;
+extern const char CATS_IMP_EXP *count_error_jobids;
+extern const char CATS_IMP_EXP *count_warning_jobs;
+extern const char CATS_IMP_EXP *count_warning_jobids;
+extern const char CATS_IMP_EXP *sum_jobs_bytes;
+extern const char CATS_IMP_EXP *sum_jobids_bytes;
+extern const char CATS_IMP_EXP *sum_jobs_files;
+extern const char CATS_IMP_EXP *sum_jobids_files;
+extern const char CATS_IMP_EXP *count_all_volumes;
+extern const char CATS_IMP_EXP *count_avl_volumes;
+extern const char CATS_IMP_EXP *count_error_volumes;
+extern const char CATS_IMP_EXP *count_full_volumes;
+extern const char CATS_IMP_EXP *count_used_volumes;
+extern const char CATS_IMP_EXP *sum_volumes_bytes;
+extern const char CATS_IMP_EXP *get_volume_size;
extern const char CATS_IMP_EXP *escape_char_value[];
recycle.c restore.c run_conf.c \
scheduler.c \
ua_acl.c ua_cmds.c ua_dotcmds.c \
- ua_query.c \
+ ua_query.c ua_collect.c \
ua_input.c ua_label.c ua_output.c ua_prune.c \
ua_purge.c ua_restore.c ua_run.c \
ua_select.c ua_server.c snapshot.c \
if (!display_runscript(hpkt)) {
first_directive = 0; /* Do not print a comma after this empty runscript */
}
+ } else if (items[item].handler == store_coll_type) {
+ display_collector_types(hpkt);
} else {
sendit(NULL, "\n \"%s\": null", items[item].name);
}
def_avalue = (alist **)((char *)(job->jobdefs) + offset);
avalue = (alist **)((char *)job + offset);
-
+
*avalue = New(alist(10, owned_by_alist));
foreach_alist(elt, (*def_avalue)) {
(*avalue)->append(bstrdup(elt));
}
set_bit(i, job->hdr.item_present);
-
+
} else if (job_items[i].handler == store_alist_res) {
void *elt;
def_avalue = (alist **)((char *)(job->jobdefs) + offset);
avalue = (alist **)((char *)job + offset);
-
+
*avalue = New(alist(10, not_owned_by_alist));
foreach_alist(elt, (*def_avalue)) {
char *configfile = NULL;
void *start_heap;
utime_t last_reload_time = 0;
-
+bstatcollect *statcollector = NULL;
+dirdstatmetrics_t dirstatmetrics;
/* Globals Imported */
extern dlist client_globals;
drop(uid, gid, false); /* reduce privileges if requested */
+ /* initialize a statistics collector */
+ initialize_statcollector();
+
/* If we are in testing mode, we don't try to fix the catalog */
cat_op mode=(test_config)?CHECK_CONNECTION:UPDATE_AND_FIX;
terminate_dird(0);
}
+ update_permanent_stats(NULL);
+
my_name_is(0, NULL, director->name()); /* set user defined name */
cleanup_old_files();
init_job_server(director->MaxConcurrentJobs);
dbg_jcr_add_hook(dir_debug_print); /* used to director variables */
- dbg_jcr_add_hook(bdb_debug_print); /* used to debug B_DB connexion after fatal signal */
+ dbg_jcr_add_hook(bdb_debug_print); /* used to debug B_DB connection after fatal signal */
+
+ start_collector_threads(); /* start collector thread for every Collector resource */
// init_device_resources();
sigprocmask(SIG_BLOCK, &set, NULL);
#endif
+ /* handle collector threads restart*/
+ terminate_collector_threads();
+
lock_jobs();
LockRes();
} else {
sched->globals = schg; /* Set globals pointer */
}
- }
+ };
}
/* Reset other globals */
SDConnectTimeout = director->SDConnectTimeout;
Dmsg0(10, "Director's configuration file reread.\n");
+ /* populate statistics data */
+ update_config_stats();
+
/* Now release saved resources, if no jobs using the resources */
if (njobs == 0) {
free_saved_resources(table);
bail_out:
UnlockRes();
unlock_jobs();
+/* start collector threads again */
+ start_collector_threads();
#if !defined(HAVE_WIN32)
sigprocmask(SIG_UNBLOCK, &set, NULL);
signal(SIGHUP, reload_config);
already_here = true;
debug_level = 0; /* turn off debug */
stop_watchdog();
+ terminate_collector_threads();
generate_daemon_event(NULL, "Exit");
unload_plugins();
if (!test_config) {
}
job_globals.destroy();
+ if (statcollector){
+ // statcollector->dump();
+ delete(statcollector);
+ }
+
close_memory_pool(); /* release free memory in pool */
lmgr_cleanup_main();
sm_dump(false);
}
}
+ /* verify a Collector resource */
+ COLLECTOR *collect;
+ foreach_res(collect, R_COLLECTOR){
+ switch (collect->type){
+ case COLLECTOR_BACKEND_CSV:
+ /* a CSV backend require a file parameter */
+ if (!collect->file){
+ Jmsg(NULL, M_FATAL, 0, _("File parameter required in Collector CSV resource \"%s\".\n"),
+ collect->hdr.name);
+ OK = false;
+ }
+ break;
+ case COLLECTOR_BACKEND_Graphite:
+ /* we require a host parameter at least */
+ if (!collect->host){
+ Jmsg(NULL, M_FATAL, 0, _("Host parameter required in Collector Graphite resource \"%s\".\n"),
+ collect->hdr.name);
+ OK = false;
+ }
+ break;
+ }
+ }
+
UnlockRes();
if (OK) {
close_msg(NULL); /* close temp message handler */
char prbuf[500];
const int nmatch = 30;
regmatch_t pmatch[nmatch];
+ berrno be;
/* Exclude spaces and look for .mail, .tmp or .restore.xx.bsr files */
const char *pat1 = "^[^ ]+\\.(restore\\.[^ ]+\\.bsr|mail|tmp)$";
uint32_t count;
};
+typedef struct {
+ int bacula_dir_config_clients;
+ int bacula_dir_config_jobs;
+ int bacula_dir_config_filesets;
+ int bacula_dir_config_pools;
+ int bacula_dir_config_schedules;
+ int bacula_dir_config_storages;
+ int bacula_dir_memory_bufs;
+ int bacula_dir_memory_heap;
+ int bacula_dir_memory_maxbufs;
+ int bacula_dir_memory_maxbytes;
+ int bacula_dir_memory_smbytes;
+ int bacula_jobs_queued_all;
+ int bacula_jobs_running_all;
+ int bacula_jobs_all;
+ int bacula_jobs_bytes;
+ int bacula_jobs_files;
+ int bacula_jobs_success_all;
+ int bacula_jobs_errors_all;
+ int bacula_jobs_warning_all;
+ int bacula_volumes_all;
+ int bacula_volumes_available;
+ int bacula_volumes_bytes;
+ int bacula_volumes_errors_all;
+ int bacula_volumes_full_all;
+ int bacula_volumes_used_all;
+} dirdstatmetrics_t;
+
void free_plugin_config_item(plugin_config_item *lst);
void free_plugin_config_items(alist *lst);
+bool update_permanent_stats(void *data);
+
+extern bstatcollect *statcollector;
+extern dirdstatmetrics_t dirstatmetrics;
#include "protos.h"
/* Message resource */
extern RES_ITEM msgs_items[];
+/* Statistics resource */
+extern RES_ITEM collector_items[];
+
/*
* This is the master resource definition.
* It must have one item for each of the resources.
{"Counter", counter_items, R_COUNTER},
{"Console", con_items, R_CONSOLE},
{"JobDefs", job_items, R_JOBDEFS},
+ {"Statistics", collector_items, R_COLLECTOR},
{"Device", NULL, R_DEVICE}, /* info obtained from SD */
{"Autochanger", store_items, R_AUTOCHANGER}, /* alias for R_STORAGE */
{NULL, NULL, 0}
sendit(sock, _(" opcmd=%s\n"), res->res_msgs.operator_cmd);
break;
+ case R_COLLECTOR:
+ dump_collector_resource(res->res_collector, sendit, sock);
+ break;
default:
sendit(sock, _("Unknown resource type %d in dump_resource.\n"), type);
break;
free_msgs_res((MSGS *)res); /* free message resource */
res = NULL;
break;
+ case R_COLLECTOR:
+ free_collector_resource(res->res_collector);
+ break;
default:
printf(_("Unknown resource type %d in free_resource.\n"), type);
}
}
res->res_sch.run = res_all.res_sch.run;
break;
- default:
+ case R_COLLECTOR:
+ if ((res = (URES *)GetResWithName(R_COLLECTOR, res_all.res_collector.hdr.name)) == NULL) {
+ Mmsg(config->m_errmsg, _("Cannot find Statistics resource %s\n"), res_all.res_collector.hdr.name);
+ return false;
+ }
+ res->res_collector.metrics = res_all.res_collector.metrics;
+ // Dmsg2(100, "metrics = 0x%p 0x%p\n", res->res_collector.metrics, res_all.res_collector.metrics);
+ break;
+
+ default:
Emsg1(M_ERROR, 0, _("Unknown resource type %d in save_resource.\n"), type);
error = true;
break;
case R_MSGS:
size = sizeof(MSGS);
break;
+ case R_COLLECTOR:
+ size = sizeof(COLLECTOR);
+ break;
case R_COUNTER:
size = sizeof(COUNTER);
break;
R_COUNTER,
R_CONSOLE,
R_JOBDEFS,
+ R_COLLECTOR,
R_DEVICE, /* This is the real last device class */
R_AUTOCHANGER, /* Alias for R_STORAGE after R_LAST */
DEVICE res_dev;
RES hdr;
RUNSCRIPT res_runscript;
+ COLLECTOR res_collector;
};
bool update_snapshot(UAContext *ua);
int prune_snapshot(UAContext *ua);
bool send_snapshot_retention(JCR *jcr, utime_t val);
+
+/* ua_collect.c */
+bool update_permanent_stats(void *data);
+void initialize_statcollector();
+void start_collector_threads();
+void terminate_collector_threads();
+void update_config_stats();
extern int sqlquery_cmd(UAContext *ua, const char *cmd);
extern int status_cmd(UAContext *ua, const char *cmd);
extern int update_cmd(UAContext *ua, const char *cmd);
+extern int collect_cmd(UAContext *ua, const char *cmd);
/* Forward referenced functions */
static int add_cmd(UAContext *ua, const char *cmd);
{ NT_("gui"), gui_cmd, _("Non-interactive gui mode"), NT_("on | off"), false},
{ NT_("help"), help_cmd, _("Print help on specific command"),
NT_("add autodisplay automount cancel create delete disable\n\tenable estimate exit gui label list llist"
- "\n\tmessages memory mount prune purge quit query\n\trestore relabel release reload run status"
+ "\n\tmessages memory mount prune purge quit query\n\trestore relabel release reload run status statistics"
"\n\tsetbandwidth setdebug setip show sqlquery time trace unmount\n\tumount update use var version wait"
"\n\tsnapshot"), false},
NT_("job=<xxx> | pool=<yyy> | fileset=<aaa> | schedule=<sss> | client=<zzz> | storage=<sss> | disabled | all"), true},
{ NT_("sqlquery"), sqlquery_cmd, _("Use SQL to query catalog"), NT_(""), false},
+ { NT_("statistics"), collect_cmd, _("Display daemon statistics data"), NT_("[ simple | full | json ] all | <metric>\n"
+ "\t[ client=<client-name> | storage=<storage-name> ]"), true},
{ NT_("time"), time_cmd, _("Print current time"), NT_(""), true},
{ NT_("trace"), trace_cmd, _("Turn on/off trace to file"), NT_("on | off"), true},
{ NT_("truncate"), truncate_cmd, _("Truncate one or more Volumes"), NT_("volume=<vol> [mediatype=<type> pool=<pool> allpools storage=<st> drive=<num>]"), true},
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2017 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Bacula Director -- User Agent Collector Commands
+ *
+ * Radosław Korzeniewski, MMXVIII
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * This is a Bacula statistics internal collector thread.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#include "bacula.h"
+#include "dird.h"
+
+/* imported variables */
+extern void *start_heap;
+
+/* imported variables */
+static bool collector_threads_started = false;
+
+/*
+ * Catalog database query handler function used for catalog metrics.
+ */
+int dirstat_handler(void *ctx, int num_fields, char **row)
+{
+ int64_t *cx = (int64_t *)ctx;
+ *cx = str_to_int64(row[0]);
+ return 0;
+}
+
+/*
+ * Updates permanent metrics based on catalog query.
+ */
+void update_permanent_metric(BDB *db, int metric, const char *sql)
+{
+ int64_t statcount;
+
+ db_sql_query(db, sql, dirstat_handler, &statcount);
+ statcollector->set_value_int64(metric, statcount);
+};
+
+/*
+ * This is a callback function executed by updatecollector_thread() every time some permanent and hard to count
+ * metrics should be collected. This include a memory allocator statistics and catalog based metrics.
+ */
+bool update_permanent_stats(void *data)
+{
+ CAT *catalog;
+
+ /* update memory statistics */
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_memory_bufs, sm_buffers);
+#ifdef HAVE_WIN32
+ uint64_t memused;
+ memused = get_memory_info(NULL, 0);
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_memory_heap, memused);
+#else
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_memory_heap, (char *)sbrk(0)-(char *)start_heap);
+#endif
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_memory_maxbufs, sm_max_buffers);
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_memory_maxbytes, sm_max_bytes);
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_memory_smbytes, sm_bytes);
+ /* Loop over databases */
+ foreach_res(catalog, R_CATALOG) {
+ BDB *db;
+ /*
+ * Make sure we can open catalog, otherwise print a warning
+ * message because the server is probably not running.
+ */
+ db = db_init_database(NULL, catalog->db_driver, catalog->db_name,
+ catalog->db_user,
+ catalog->db_password, catalog->db_address,
+ catalog->db_port, catalog->db_socket,
+ catalog->db_ssl_mode, catalog->db_ssl_key,
+ catalog->db_ssl_cert, catalog->db_ssl_ca,
+ catalog->db_ssl_capath, catalog->db_ssl_cipher,
+ catalog->mult_db_connections,
+ catalog->disable_batch_insert);
+ if (!db || !db_open_database(NULL, db)) {
+ if (db) {
+ db_close_database(NULL, db);
+ }
+ continue;
+ }
+ /* update permanent runtime metrics from catalog */
+ update_permanent_metric(db, dirstatmetrics.bacula_jobs_all, count_all_jobs);
+ update_permanent_metric(db, dirstatmetrics.bacula_jobs_success_all, count_success_jobs);
+ update_permanent_metric(db, dirstatmetrics.bacula_jobs_errors_all, count_error_jobs);
+ update_permanent_metric(db, dirstatmetrics.bacula_jobs_warning_all, count_warning_jobs);
+ update_permanent_metric(db, dirstatmetrics.bacula_jobs_files, sum_jobs_files);
+ update_permanent_metric(db, dirstatmetrics.bacula_jobs_bytes, sum_jobs_bytes);
+ update_permanent_metric(db, dirstatmetrics.bacula_volumes_all, count_all_volumes);
+ update_permanent_metric(db, dirstatmetrics.bacula_volumes_bytes, sum_volumes_bytes);
+ update_permanent_metric(db, dirstatmetrics.bacula_volumes_available, count_avl_volumes);
+ update_permanent_metric(db, dirstatmetrics.bacula_volumes_errors_all, count_error_volumes);
+ update_permanent_metric(db, dirstatmetrics.bacula_volumes_full_all, count_full_volumes);
+ update_permanent_metric(db, dirstatmetrics.bacula_volumes_used_all, count_used_volumes);
+
+ if (db) db_close_database(NULL, db);
+ };
+ return true;
+};
+
+/*
+ * Initialize a daemon wide Bacula statcollector.
+ * Creates a global daemon collector and registers all global metrics.
+ * This is a statcollector initialization function specific for Director.
+ */
+void initialize_statcollector()
+{
+ /* create a statistics collector */
+ statcollector = New(bstatcollect);
+ /* register config metrics */
+ dirstatmetrics.bacula_dir_config_clients =
+ statcollector->registration_int64("bacula.dir.config.clients", METRIC_UNIT_CLIENT,
+ ((rblist *)res_head[R_CLIENT-r_first]->res_list)->size(),
+ "The number of defined clients in Director.");
+ dirstatmetrics.bacula_dir_config_jobs =
+ statcollector->registration_int64("bacula.dir.config.jobs", METRIC_UNIT_JOB,
+ ((rblist *)res_head[R_JOB-r_first]->res_list)->size(),
+ "The number of defined jobs in Director.");
+ dirstatmetrics.bacula_dir_config_filesets =
+ statcollector->registration_int64("bacula.dir.config.filesets", METRIC_UNIT_FILESET,
+ ((rblist *)res_head[R_FILESET-r_first]->res_list)->size(),
+ "The number of defined filesets in Director.");
+ dirstatmetrics.bacula_dir_config_pools =
+ statcollector->registration_int64("bacula.dir.config.pools", METRIC_UNIT_POOL,
+ ((rblist *)res_head[R_POOL-r_first]->res_list)->size(),
+ "The number of defined pools in Director.");
+ dirstatmetrics.bacula_dir_config_schedules =
+ statcollector->registration_int64("bacula.dir.config.schedules", METRIC_UNIT_SCHEDULE,
+ ((rblist *)res_head[R_SCHEDULE-r_first]->res_list)->size(),
+ "The number of defined schedules in Director.");
+ dirstatmetrics.bacula_dir_config_storages =
+ statcollector->registration_int64("bacula.dir.config.storages", METRIC_UNIT_STORAGE,
+ ((rblist *)res_head[R_STORAGE-r_first]->res_list)->size(),
+ "The number of defined storages in Director.");
+ /* register runtime metrics */
+ dirstatmetrics.bacula_jobs_queued_all =
+ statcollector->registration_int64("bacula.jobs.queued.all", METRIC_UNIT_JOB,
+ 0, "The number of currently queued jobs.");
+ dirstatmetrics.bacula_jobs_running_all =
+ statcollector->registration_int64("bacula.jobs.running.all", METRIC_UNIT_JOB,
+ 0, "The number of currently running jobs.");
+ /* register permanent runtime metrics - from catalog */
+ dirstatmetrics.bacula_jobs_all =
+ statcollector->registration("bacula.jobs.all", METRIC_INT, METRIC_UNIT_JOB,
+ "Number of all jobs.");
+ dirstatmetrics.bacula_jobs_success_all =
+ statcollector->registration("bacula.jobs.success.all", METRIC_INT, METRIC_UNIT_JOB,
+ "Number of successful jobs.");
+ dirstatmetrics.bacula_jobs_errors_all =
+ statcollector->registration("bacula.jobs.error.all", METRIC_INT, METRIC_UNIT_JOB,
+ "Number of error jobs.");
+ dirstatmetrics.bacula_jobs_warning_all =
+ statcollector->registration("bacula.jobs.warning.all", METRIC_INT, METRIC_UNIT_JOB,
+ "Number of warning jobs.");
+ dirstatmetrics.bacula_jobs_files =
+ statcollector->registration("bacula.jobs.files", METRIC_INT, METRIC_UNIT_FILE,
+ "Number of all jobs files.");
+ dirstatmetrics.bacula_jobs_bytes =
+ statcollector->registration("bacula.jobs.bytes", METRIC_INT, METRIC_UNIT_BYTE,
+ "Sum size of all jobs.");
+ /* register volumes information */
+ dirstatmetrics.bacula_volumes_all =
+ statcollector->registration("bacula.volumes.all", METRIC_INT, METRIC_UNIT_VOLUME,
+ "Number of all defined volumes.");
+ dirstatmetrics.bacula_volumes_available =
+ statcollector->registration("bacula.volumes.available", METRIC_INT, METRIC_UNIT_VOLUME,
+ "Number of all available volumes.");
+ dirstatmetrics.bacula_volumes_bytes =
+ statcollector->registration("bacula.volumes.bytes", METRIC_INT, METRIC_UNIT_BYTE,
+ "Sum size of all volumes written.");
+ dirstatmetrics.bacula_volumes_errors_all =
+ statcollector->registration("bacula.volumes.errors.all", METRIC_INT, METRIC_UNIT_VOLUME,
+ "Number of Error volumes.");
+ dirstatmetrics.bacula_volumes_full_all =
+ statcollector->registration("bacula.volumes.full.all", METRIC_INT, METRIC_UNIT_VOLUME,
+ "Number of Full volumes.");
+ dirstatmetrics.bacula_volumes_used_all =
+ statcollector->registration("bacula.volumes.used.all", METRIC_INT, METRIC_UNIT_VOLUME,
+ "Number of Used volumes.");
+ /* register memory utilization */
+ dirstatmetrics.bacula_dir_memory_bufs =
+ statcollector->registration("bacula.dir.memory.bufs", METRIC_INT, METRIC_UNIT_NUMBER,
+ "The number of buffers allocated.");
+ dirstatmetrics.bacula_dir_memory_heap =
+ statcollector->registration("bacula.dir.memory.heap", METRIC_INT, METRIC_UNIT_BYTE,
+ "The size of the heap.");
+ dirstatmetrics.bacula_dir_memory_maxbufs =
+ statcollector->registration("bacula.dir.memory.maxbufs", METRIC_INT, METRIC_UNIT_NUMBER,
+ "The maximum buffers allocated.");
+ dirstatmetrics.bacula_dir_memory_maxbytes =
+ statcollector->registration("bacula.dir.memory.maxbytes", METRIC_INT, METRIC_UNIT_BYTE,
+ "The allocated memory size.");
+ dirstatmetrics.bacula_dir_memory_smbytes =
+ statcollector->registration("bacula.dir.memory.smbytes", METRIC_INT, METRIC_UNIT_BYTE,
+ "The allocated memory size.");
+ // statcollector->dump();
+};
+
+/*
+ * Starts a single thread for every defined Statistics resource and one globally single thread for
+ * Update Statistics if at least one Statistics resource is available.
+ */
+void start_collector_threads()
+{
+ COLLECTOR *collect;
+ utime_t interval = 24*3600; // the minimal update interval is 1 day
+ UPDATE_COLLECTOR_INIT_t initdata;
+
+ foreach_res(collect, R_COLLECTOR){
+ interval = MIN(interval, collect->interval);
+ collect->statcollector = statcollector;
+ collect->spool_directory = working_directory;
+ collect->daemon = "bacula-dir";
+ collect->jcr = new_jcr(sizeof(JCR), dird_free_jcr);
+ Dmsg1(100, "Starting statistics thread for \"%s\"\n", collect->name());
+ start_collector_thread(collect);
+ collector_threads_started = true;
+ };
+ if (((rblist *)res_head[R_COLLECTOR-r_first]->res_list)->size() > 0){
+ initdata.interval = interval;
+ initdata.jcr = new_jcr(sizeof(JCR), dird_free_jcr);
+ initdata.routine = &update_permanent_stats;
+ initdata.data = NULL;
+ /* start update thread */
+ start_updcollector_thread(initdata);
+ }
+};
+
+/*
+ * Terminates all Statistics's threads executed.
+ */
+void terminate_collector_threads()
+{
+ COLLECTOR *collect;
+
+ if (collector_threads_started){
+ foreach_res(collect, R_COLLECTOR){
+ stop_collector_thread(collect);
+ };
+ stop_updcollector_thread();
+ collector_threads_started = false;
+ }
+};
+
+/*
+ * Updates metrics values based on the configuration resources.
+ */
+void update_config_stats()
+{
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_config_clients,
+ ((rblist *)res_head[R_CLIENT-r_first]->res_list)->size());
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_config_jobs,
+ ((rblist *)res_head[R_JOB-r_first]->res_list)->size());
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_config_filesets,
+ ((rblist *)res_head[R_FILESET-r_first]->res_list)->size());
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_config_pools,
+ ((rblist *)res_head[R_POOL-r_first]->res_list)->size());
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_config_schedules,
+ ((rblist *)res_head[R_SCHEDULE-r_first]->res_list)->size());
+ statcollector->set_value_int64(dirstatmetrics.bacula_dir_config_storages,
+ ((rblist *)res_head[R_STORAGE-r_first]->res_list)->size());
+};
+
+/*
+ * Display a text representation of the metric for UA.
+ *
+ * in:
+ * ua - an UA context class for sending rendered text to UA
+ * m - metric to display
+ * format - a display format
+ * out:
+ * metric rendered and sent to the UA
+ */
+void display_metric(UAContext *ua, bstatmetric *m, display_format_t format, int nr)
+{
+ POOL_MEM out(PM_MESSAGE);
+
+ rendermetric(out, m, format, nr);
+ ua->send_msg(out.c_str());
+};
+
+/*
+ * Sends a collect command to client.
+ *
+ * in:
+ * ua - an UA context class for sending rendered text to UA
+ * client - a CLIENT class which we are query
+ * doall - when true the "collect all" command should be executed else render specific metric
+ * format - a display format
+ * margc, margk - a command argc/argk parameters
+ * out:
+ * collect command handled by a client
+ */
+void do_collect_client(UAContext *ua, CLIENT *client, bool doall, display_format_t format, int margc, char**margk)
+{
+ int i;
+ BSOCK *fd;
+ POOL_MEM buf;
+
+ if (!acl_access_client_ok(ua, client->name(), JT_BACKUP_RESTORE)) {
+ ua->error_msg(_("No authorization for Client \"%s\"\n"), client->name());
+ return;
+ }
+ /* Connect to File daemon */
+ ua->jcr->client = client;
+ /* Release any old dummy key */
+ if (ua->jcr->sd_auth_key) {
+ free(ua->jcr->sd_auth_key);
+ }
+ /* Create a new dummy SD auth key */
+ ua->jcr->sd_auth_key = bstrdup("dummy");
+
+ /* Try to connect for 15 seconds */
+ if (!ua->api) ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
+ client->name(), client->address(buf.addr()), client->FDport);
+ if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
+ ua->send_msg(_("Failed to connect to Client %s.\n====\n"),
+ client->name());
+ free_bsock(ua->jcr->file_bsock);
+ return;
+ }
+ Dmsg0(20, _("Connected to file daemon\n"));
+ fd = ua->jcr->file_bsock;
+ /* command */
+ if (doall){
+ fd->fsend(collect_all_send_cmd, displayformat2str(format));
+ } else {
+ Mmsg(buf, collect_metrics_send_cmd, displayformat2str(format));
+ for (i = 0; i < margc; i++) {
+ pm_strcat(buf, " ");
+ pm_strcat(buf, margk[i]);
+ }
+ pm_strcat(buf, "\n");
+ fd->fsend(buf.c_str());
+ }
+ while (fd->recv() >= 0) {
+ ua->send_msg("%s", fd->msg);
+ }
+ fd->signal(BNET_TERMINATE);
+ free_bsock(ua->jcr->file_bsock);
+ return;
+};
+
+/*
+ * Sends a collect command to storage.
+ *
+ * in:
+ * ua - an UA context class for sending rendered text to UA
+ * client - a CLIENT class which we are query
+ * doall - when true the "collect all" command should be executed else render specific metric
+ * format - a display format
+ * margc, margk - a command argc/argk parameters
+ * out:
+ * collect command handled by a storage
+ */
+void do_collect_storage(UAContext *ua, STORE *store, bool doall, display_format_t format, int margc, char**margk)
+{
+ int i;
+ BSOCK *sd;
+ USTORE lstore;
+ POOL_MEM buf;
+
+ if (!acl_access_ok(ua, Storage_ACL, store->name())) {
+ ua->error_msg(_("No authorization for Storage \"%s\"\n"), store->name());
+ return;
+ }
+ /*
+ * The Storage daemon is problematic because it shows information
+ * related to multiple Job, so if there is a Client or Job
+ * ACL restriction, we forbid all access to the Storage.
+ */
+ if (have_restricted_acl(ua, Client_ACL) ||
+ have_restricted_acl(ua, Job_ACL)) {
+ ua->error_msg(_("Restricted Client or Job does not permit access to Storage daemons\n"));
+ return;
+ }
+ lstore.store = store;
+ pm_strcpy(lstore.store_source, _("unknown source"));
+ set_wstorage(ua->jcr, &lstore);
+ /* Try connecting for up to 15 seconds */
+ if (!ua->api) ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"),
+ store->name(), store->address, store->SDport);
+ if (!connect_to_storage_daemon(ua->jcr, 1, 15, 0)) {
+ ua->send_msg(_("\nFailed to connect to Storage daemon %s.\n====\n"),
+ store->name());
+ free_bsock(ua->jcr->store_bsock);
+ return;
+ }
+ Dmsg0(20, "Connected to storage daemon\n");
+ sd = ua->jcr->store_bsock;
+ /* command */
+ if (doall){
+ sd->fsend(collect_all_send_cmd, displayformat2str(format));
+ } else {
+ Mmsg(buf, collect_metrics_send_cmd, displayformat2str(format));
+ for (i = 0; i < margc; i++) {
+ pm_strcat(buf, " ");
+ pm_strcat(buf, margk[i]);
+ }
+ pm_strcat(buf, "\n");
+ sd->fsend(buf.c_str());
+ }
+ while (sd->recv() >= 0) {
+ ua->send_msg("%s", sd->msg);
+ }
+ sd->signal(BNET_TERMINATE);
+ free_bsock(ua->jcr->store_bsock);
+ return;
+};
+
+/*
+ * A collect command handled by director.
+ * If the command points to "dir" then it will be executed on Director. The "client[=name]" parameter
+ * points to the client resource and storage[=name] will point to the storage resource.
+ *
+ * in:
+ * ua - an UA context class for sending rendered text to UA
+ * cmd - a command string to execute
+ * out:
+ * collect command handled by dir/client/storage depends on command parameter
+ */
+int collect_cmd(UAContext *ua, const char *cmd)
+{
+ int i, nr, margc;
+ alist *list;
+ bstatmetric *item;
+ bool doall = true;
+ display_format_t format = COLLECT_SIMPLE;
+ char **margk;
+ STORE *store = NULL;
+ CLIENT *client = NULL;
+
+ Dmsg1(20, "cmd:%s:\n", cmd);
+
+ margk = (char**)malloc(ua->argc * sizeof(char*));
+ margc = 0;
+ /* scan all parameters to set flags */
+ for (i = 1; i < ua->argc; i++){
+ Dmsg1(20, "statistics process param: %s\n", ua->argk[i]);
+ if (strcasecmp(ua->argk[i], "all") == 0){
+ doall = true;
+ Dmsg0(20, "statistics: param doall\n");
+ continue;
+ }
+ if (strcasecmp(ua->argk[i], "dir") == 0){
+ /* this is a default */
+ Dmsg0(20, "statistics: dir\n");
+ continue;
+ }
+ if (strcasecmp(ua->argk[i], "simple") == 0){
+ /* this is a default */
+ Dmsg0(20, "statistics: Simple format\n");
+ continue;
+ }
+ if (strcasecmp(ua->argk[i], "full") == 0){
+ format = COLLECT_FULL;
+ Dmsg0(20, "statistics format Full\n");
+ continue;
+ }
+
+ if (strcasecmp(ua->argk[i], "json") == 0){
+ format = COLLECT_JSON;
+ Dmsg0(20, "statistics format JSON\n");
+ continue;
+ }
+ if (strcasecmp(ua->argk[i], "client") == 0 && store == NULL) {
+ client = get_client_resource(ua, JT_BACKUP_RESTORE);
+ if (!client) {
+ goto bailout;
+ }
+ continue;
+ }
+ if (strcasecmp(ua->argk[i], "storage") == 0 && client == NULL) {
+ store = get_storage_resource(ua, false /*no default*/, true/*unique*/);
+ if (!store) {
+ goto bailout;
+ }
+ continue;
+ }
+ /* everything else is a metric to display */
+ Dmsg1(20, "statistics add metric: %s\n", ua->argk[i]);
+ margk[margc++] = ua->argk[i];
+ doall = false;
+ }
+
+ if (doall){
+ Dmsg0(20, "statistics default doall!\n");
+ }
+
+ /* If no args, ask for status type */
+ if (ua->argc == 1) {
+ char prmt[MAX_NAME_LENGTH];
+
+ start_prompt(ua, _("Statistics available for:\n"));
+ add_prompt(ua, NT_("Director"));
+ add_prompt(ua, NT_("Storage"));
+ add_prompt(ua, NT_("Client"));
+ Dmsg0(20, "do_prompt: select daemon\n");
+ if ((i=do_prompt(ua, "", _("Select daemon type for statistics"), prmt, sizeof(prmt))) < 0) {
+ goto bailout;
+ }
+ Dmsg1(20, "item=%d\n", i);
+ switch (i) {
+ case 0: /* Director, the default behavior */
+ break;
+ case 1:
+ store = select_storage_resource(ua, true/*unique*/);
+ if (!store) {
+ goto bailout;
+ }
+ break;
+ case 2:
+ client = select_client_resource(ua, JT_BACKUP_RESTORE);
+ if (!client) {
+ goto bailout;
+ }
+ break;
+ default:
+ return 1;
+ }
+ }
+
+ if (client){
+ /* do collect client */
+ do_collect_client(ua, client, doall, format, margc, margk);
+ } else
+ if (store){
+ /* do collect storage */
+ do_collect_storage(ua, store, doall, format, margc, margk);
+ } else {
+ /* it is simpler to handle JSON array here */
+ if (format == COLLECT_JSON){
+ ua->send_msg("[");
+ }
+ /* collect director */
+ update_permanent_stats(NULL);
+ nr = 0;
+ if (doall){
+ /* we should display all the metrics found */
+ list = statcollector->get_all();
+ foreach_alist(item, list){
+ display_metric(ua, item, format, nr++);
+ }
+ free_metric_alist(list);
+ } else {
+ /* we should display selected metrics only */
+ for (i = 0; i < margc; i++) {
+ /* get a single metric */
+ item = statcollector->get_metric(margk[i]);
+ if (item){
+ display_metric(ua, item, format, nr++);
+ delete(item);
+ }
+ }
+ }
+ /* it is simpler to handle JSON array here */
+ if (format == COLLECT_JSON){
+ ua->send_msg("\n]\n");
+ }
+ }
+bailout:
+ if (margk){
+ free(margk);
+ }
+ return 1;
+}
{NT_("filesets"), R_FILESET},
{NT_("pools"), R_POOL},
{NT_("messages"), R_MSGS},
+ {NT_("statistics"), R_COLLECTOR},
// {NT_("consoles"), R_CONSOLE},
// {NT_("jobdefs"), R_JOBDEFS},
// {NT_{"autochangers"), R_AUTOCHANGER},
static void llist_scheduled_jobs(UAContext *ua);
static void list_running_jobs(UAContext *ua);
static void list_terminated_jobs(UAContext *ua);
+static void list_collectors_status(UAContext *ua);
+static void api_collectors_status(UAContext *ua, char *collname);
static void do_storage_status(UAContext *ua, STORE *store, char *cmd);
static void do_client_status(UAContext *ua, CLIENT *client, char *cmd);
static void do_director_status(UAContext *ua);
list_running_jobs(ua);
} else if (strcasecmp(ua->argk[2], "terminated") == 0) {
list_terminated_jobs(ua);
+ } else if (strcasecmp(ua->argk[2], "statistics") == 0) {
+ list_collectors_status(ua);
} else {
ua->send_msg("1900 Bad .status command, wrong argument.\n");
return false;
{
BSOCK *sd;
USTORE lstore;
-
+ int i;
if (!acl_access_ok(ua, Storage_ACL, store->name())) {
ua->error_msg(_("No authorization for Storage \"%s\"\n"), store->name());
sd->fsend(".status %s %s %s api=%d api_opts=%s",
cmd, ua->argk[3], devname.c_str(),
ua->api, ua->api_opts);
+ } else if (strcasecmp(cmd, "collector") == 0){
+ i = find_arg_with_value(ua, "collector");
+ if (i>0){
+ Mmsg(devname, "%s", ua->argv[i]);
+ bash_spaces(devname.c_str());
+ sd->fsend(".status %s=%s api=%d api_opts=%s",
+ cmd, devname.c_str(), ua->api, ua->api_opts);
+ } else {
+ sd->fsend(".status %s api=%d api_opts=%s",
+ cmd, ua->api, ua->api_opts);
+ }
} else {
- int i = find_arg_with_value(ua, "device");
+ i = find_arg_with_value(ua, "device");
if (i>0) {
Mmsg(devname, "device=%s", ua->argv[i]);
bash_spaces(devname.c_str());
{
BSOCK *fd;
POOL_MEM buf;
+ int i;
if (!acl_access_client_ok(ua, client->name(), JT_BACKUP_RESTORE)) {
ua->error_msg(_("No authorization for Client \"%s\"\n"), client->name());
Dmsg0(20, _("Connected to file daemon\n"));
fd = ua->jcr->file_bsock;
if (cmd) {
- fd->fsend(".status %s api=%d api_opts=%s", cmd, ua->api, ua->api_opts);
+ if (strcasecmp(cmd, "collector") == 0 && (i = find_arg_with_value(ua, "collector")) > 0){
+ Mmsg(buf, "%s", ua->argv[i]);
+ bash_spaces(buf.c_str());
+ fd->fsend(".status %s=%s api=%d api_opts=%s", cmd, buf.c_str(), ua->api, ua->api_opts);
+ } else {
+ fd->fsend(".status %s api=%d api_opts=%s", cmd, ua->api, ua->api_opts);
+ }
} else {
fd->fsend("status");
}
}
unlock_last_jobs_list();
}
+
+static void list_collectors_status(UAContext *ua)
+{
+ URES *res;
+ int i;
+ char *collname = NULL;
+ POOL_MEM buf(PM_MESSAGE);
+
+ if ((i = find_arg_with_value(ua, "statistics")) >= 0) {
+ collname = ua->argv[i];
+ }
+
+ Dmsg2(200, "enter list_collectors_status() api=%i coll=%s\n", ua->api, NPRTB(collname));
+ if (ua->api > 1) {
+ api_collectors_status(ua, collname);
+ return;
+ }
+
+ LockRes();
+ foreach_res(res, R_COLLECTOR) {
+ if (collname && !bstrcmp(collname, res->res_collector.hdr.name)){
+ continue;
+ }
+ Dmsg1(500, "processing: %s\n", res->res_collector.hdr.name);
+ render_collector_status(res->res_collector, buf);
+ ua->send_msg(buf.c_str());
+ };
+ UnlockRes();
+ if (!collname){
+ render_updcollector_status(buf);
+ ua->send_msg(buf.c_str());
+ }
+ Dmsg0(200, "leave list_collectors_status()\n");
+}
+
+static void api_collectors_status(UAContext *ua, char *collname)
+{
+ URES *res;
+ OutputWriter ow(ua->api_opts);
+ POOLMEM *buf;
+
+ Dmsg1(200, "enter api_collectors_status() %s\n", NPRTB(collname));
+ ow.start_group("collector_backends");
+ LockRes();
+ foreach_res(res, R_COLLECTOR) {
+ if (collname && !bstrcmp(collname, res->res_collector.hdr.name)){
+ continue;
+ }
+ Dmsg1(500, "processing: %s\n", res->res_collector.hdr.name);
+ api_render_collector_status(res->res_collector, ow);
+ };
+ UnlockRes();
+ buf = ow.end_group();
+ if (!collname){
+ ow.start_group("collector_update");
+ api_render_updcollector_status(ow);
+ buf = ow.end_group();
+ }
+ ua->send_msg(buf);
+ Dmsg0(200, "leave api_collectors_status()\n");
+};
#
SVRSRCS = filed.c authenticate.c backup.c crypto.c \
- win_efs.c estimate.c \
+ win_efs.c estimate.c fdcollect.c \
fd_plugins.c accurate.c \
filed_conf.c heartbeat.c hello.c job.c fd_snapshot.c \
restore.c status.c suspend.c verify.c verify_vol.c \
Public License, v3.0 ("AGPLv3") and some additional permissions and
terms pursuant to its AGPLv3 Section 7.
- This notice must be preserved when any source code is
+ This notice must be preserved when any source code is
conveyed and/or propagated.
Bacula(R) is a registered trademark of Kern Sibbald.
display_cipher(hpkt);
} else if (items[item].handler == store_digest_type) {
display_digest(hpkt);
+ } else if (items[item].handler == store_coll_type) {
+ display_collector_types(hpkt);
} else {
printf("\n \"%s\": null", items[item].name);
}
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2017 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Radosław Korzeniewski, MMXVIII
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * This is a Bacula statistics File Daemon collector routines.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#include "bacula.h"
+#include "filed.h"
+
+/* imported functions and variables */
+extern void *start_heap;
+extern const char collect_all_cmd[];
+extern const char collect_metrics_cmd[];
+
+static bool collector_threads_started = false;
+
+/*
+ * Initialize a daemon wide Bacula statcollector.
+ * Creates a global daemon collector and registers all global metrics.
+ * This is a statcollector initialization function specific for FileDaemon.
+ */
+void initialize_statcollector()
+{
+ POOL_MEM met(PM_NAME);
+
+ /* create a statistics collector */
+ statcollector = New(bstatcollect);
+ /* memory utilization */
+ Mmsg(met, "bacula.client.%s.memory.bufs", me->hdr.name);
+ fdstatmetrics.bacula_client_memory_bufs =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_NUMBER,
+ "The number of buffers allocated.");
+ Mmsg(met, "bacula.client.%s.memory.heap", me->hdr.name);
+ fdstatmetrics.bacula_client_memory_heap =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ "The size of the heap.");
+ Mmsg(met, "bacula.client.%s.memory.maxbufs", me->hdr.name);
+ fdstatmetrics.bacula_client_memory_maxbufs =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_NUMBER,
+ "The maximum buffers allocated.");
+ Mmsg(met, "bacula.client.%s.memory.maxbytes", me->hdr.name);
+ fdstatmetrics.bacula_client_memory_maxbytes =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ "The allocated memory size.");
+ Mmsg(met, "bacula.client.%s.memory.smbytes", me->hdr.name);
+ fdstatmetrics.bacula_client_memory_smbytes =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ "The allocated memory size.");
+ // statcollector->dump();
+};
+
+/*
+ * This is a callback function executed by updatecollector_thread() every time some permanent and hard to count
+ * metrics should be collected. This include a memory allocator statistics.
+ */
+bool update_permanent_stats(void *data)
+{
+ /* update memory statistics */
+ statcollector->set_value_int64(fdstatmetrics.bacula_client_memory_bufs, sm_buffers);
+#ifdef HAVE_WIN32
+ uint64_t memused;
+ memused = get_memory_info(NULL, 0);
+ statcollector->set_value_int64(fdstatmetrics.bacula_client_memory_heap, memused);
+#else
+ statcollector->set_value_int64(fdstatmetrics.bacula_client_memory_heap, (char *)sbrk(0)-(char *)start_heap);
+#endif
+ statcollector->set_value_int64(fdstatmetrics.bacula_client_memory_maxbufs, sm_max_buffers);
+ statcollector->set_value_int64(fdstatmetrics.bacula_client_memory_maxbytes, sm_max_bytes);
+ statcollector->set_value_int64(fdstatmetrics.bacula_client_memory_smbytes, sm_bytes);
+ return true;
+};
+
+/*
+ * Starts a single thread for every defined Statistics resource and one globally single thread for
+ * Update Statistics if at least one Statistics resource is available.
+ */
+void start_collector_threads()
+{
+ COLLECTOR *collect;
+ utime_t interval = 24*3600; // the minimal update interval is 1 day
+ UPDATE_COLLECTOR_INIT_t initdata;
+
+ foreach_res(collect, R_COLLECTOR){
+ interval = MIN(interval, collect->interval);
+ collect->statcollector = statcollector;
+ collect->spool_directory = working_directory;
+ collect->daemon = "bacula-fd";
+ collect->jcr = new_jcr(sizeof(JCR), filed_free_jcr);
+ Dmsg1(100, "Starting statistics thread for \"%s\"\n", collect->name());
+ start_collector_thread(collect);
+ collector_threads_started = true;
+ };
+ if (((rblist *)res_head[R_COLLECTOR-r_first]->res_list)->size() > 0){
+ initdata.interval = interval;
+ initdata.jcr = new_jcr(sizeof(JCR), filed_free_jcr);
+ initdata.routine = &update_permanent_stats;
+ initdata.data = NULL;
+ /* start update thread */
+ start_updcollector_thread(initdata);
+ }
+};
+
+/*
+ * Terminates all Statistics's threads executed.
+ */
+void terminate_collector_threads()
+{
+ COLLECTOR *collect;
+
+ if (collector_threads_started){
+ foreach_res(collect, R_COLLECTOR){
+ stop_collector_thread(collect);
+ };
+ stop_updcollector_thread();
+ collector_threads_started = false;
+ }
+};
+
+/*
+ * Display a text representation of the metric for UA.
+ *
+ * in:
+ * user - a BSOCK class for sending rendered text to UA
+ * m - metric to display
+ * format - a display format
+ * out:
+ * metric rendered and sent to the UA
+ */
+void display_metric(BSOCK *user, bstatmetric *m, display_format_t format, int nr)
+{
+ POOL_MEM out(PM_MESSAGE);
+ int len;
+
+ rendermetric(out, m, format, nr);
+ len = strlen(out.c_str()) + 1;
+ user->msg = check_pool_memory_size(user->msg, len);
+ memcpy(user->msg, out.c_str(), len);
+ user->msglen = len;
+ user->send();
+};
+
+/*
+ * Collect command from Director
+ */
+int collect_cmd(JCR *jcr)
+{
+ BSOCK *dir = jcr->dir_bsock;
+ POOLMEM *cmd;
+ POOLMEM *fmt;
+ POOLMEM *metrics = NULL;
+ char *ch;
+ char *met;
+ bool doall = true;
+ display_format_t format;
+ bstatmetric *item;
+ alist *list;
+ int nr;
+
+ cmd = get_memory(dir->msglen+1);
+ fmt = get_memory(dir->msglen+1);
+
+ Dmsg1(100, "collect cmd: %s\n", dir->msg);
+ if (sscanf(dir->msg, collect_all_cmd, fmt) != 1) {
+ if (sscanf(dir->msg, collect_metrics_cmd, fmt, cmd) != 2) {
+ pm_strcpy(&jcr->errmsg, dir->msg);
+ Jmsg1(jcr, M_FATAL, 0, _("Bad statistics command: %s\n"), jcr->errmsg);
+ dir->fsend(_("2900 Bad statistics command, missing argument.\n"));
+ dir->signal(BNET_EOD);
+ free_memory(cmd);
+ free_memory(fmt);
+ return 0;
+ } else {
+ doall = false;
+ metrics = strstr(dir->msg, cmd);
+ pm_strcpy(cmd, metrics);
+ metrics = cmd;
+ }
+ }
+
+ format = scandisplayformat(fmt);
+ if (!doall) Dmsg1(100, "statistics metrics: %s\n", metrics);
+ if (format == COLLECT_JSON){
+ dir->fsend("[");
+ }
+ update_permanent_stats(NULL);
+ nr = 0;
+ if (doall){
+ /* we should display all the metrics found */
+ list = statcollector->get_all();
+ foreach_alist(item, list){
+ display_metric(dir, item, format, nr++);
+ }
+ free_metric_alist(list);
+ } else {
+ /* we should display selected metrics only */
+ met = metrics;
+ /* eliminate the EOL char in metrics */
+ ch = strchr(met, '\n');
+ if (ch){
+ *ch = ' ';
+ }
+ while (*met != 0 && (ch = strchr(met, ' ')) != NULL){
+ /* prepare and get a single metric */
+ *ch = '\0';
+ Dmsg1(100, "statistics get metric: %s\n", met);
+ item = statcollector->get_metric(met);
+ if (item){
+ Dmsg0(200, "metric found\n");
+ display_metric(dir, item, format, nr++);
+ delete(item);
+ }
+ met = ch + 1;
+ }
+ }
+ if (format == COLLECT_JSON){
+ dir->fsend("\n]\n");
+ }
+
+ dir->signal(BNET_EOD);
+ free_memory(cmd);
+ free_memory(fmt);
+ return 1;
+}
bool no_signals = false;
void *start_heap;
extern struct s_cmds cmds[];
+bstatcollect *statcollector = NULL;
+fdstatmetrics_t fdstatmetrics;
#ifndef CONFIG_FILE /* Might be overwritten */
#define CONFIG_FILE "bacula-fd.conf" /* default config file */
exit(1);
}
-
/*********************************************************************
*
* Main Bacula Unix Client Program
*
*/
-
#if defined(HAVE_WIN32)
#define main BaculaMain
#endif
#ifdef BOMB
me += 1000000;
#endif
+ /* initialize a statistics collector */
+ initialize_statcollector();
/* Setup default value for the the snapshot handler */
if (!me->snapshot_command) {
start_watchdog(); /* start watchdog thread */
init_jcr_subsystem(); /* start JCR watchdogs etc. */
}
+
+ start_collector_threads(); /* start collector thread for every Collector resource */
+
server_tid = pthread_self();
/* Become server, and handle requests */
already_here = true;
debug_level = 0; /* turn off debug */
stop_watchdog();
+ terminate_collector_threads();
bnet_stop_thread_server(server_tid);
generate_daemon_event(NULL, "Exit");
delete config;
config = NULL;
}
+ if (statcollector){
+ // statcollector->dump();
+ delete(statcollector);
+ }
term_msg();
cleanup_crypto();
free(res_head);
int (*func)(JCR *);
int access; /* specify if monitors/restricted have access to this function */
};
+
+typedef struct {
+ int bacula_client_memory_bufs;
+ int bacula_client_memory_heap;
+ int bacula_client_memory_maxbufs;
+ int bacula_client_memory_maxbytes;
+ int bacula_client_memory_smbytes;
+ int bacula_client_test_metric;
+ int bacula_client_test_metric2;
+} fdstatmetrics_t;
+
+void allow_os_suspensions();
+void prevent_os_suspensions();
+bool update_permanent_stats(void *data);
+
+extern bstatcollect *statcollector;
+extern fdstatmetrics_t fdstatmetrics;
/* Message resource */
extern RES_ITEM msgs_items[];
+/* Statistics resource */
+extern RES_ITEM collector_items[];
+
+
/*
* This is the master resource definition.
* It must have one item for each of the resources.
*/
RES_TABLE resources[] = {
- {"Director", dir_items, R_DIRECTOR},
- {"FileDaemon", cli_items, R_CLIENT},
- {"Messages", msgs_items, R_MSGS},
- {"Console", cons_items, R_CONSOLE},
- {"Client", cli_items, R_CLIENT}, /* alias for filedaemon */
- {NULL, NULL, 0}
+ {"Director", dir_items, R_DIRECTOR},
+ {"FileDaemon", cli_items, R_CLIENT},
+ {"Messages", msgs_items, R_MSGS},
+ {"Console", cons_items, R_CONSOLE},
+ {"Statistics", collector_items, R_COLLECTOR},
+ {"Client", cli_items, R_CLIENT}, /* alias for filedaemon */
+ {NULL, NULL, 0}
};
struct s_ct ciphertypes[] = {
if (res->res_msgs.operator_cmd)
sendit(sock, " opcmd=%s\n", res->res_msgs.operator_cmd);
break;
+ case R_COLLECTOR:
+ dump_collector_resource(res->res_collector, sendit, sock);
+ break;
default:
sendit(sock, "Unknown resource type %d\n", type);
}
free_msgs_res((MSGS *)res); /* free message resource */
res = NULL;
break;
+ case R_COLLECTOR:
+ free_collector_resource(res->res_collector);
+ break;
default:
printf(_("Unknown resource type %d\n"), type);
}
res->res_client.messages = res_all.res_client.messages;
res->res_client.disable_cmds = res_all.res_client.disable_cmds;
break;
+ case R_COLLECTOR:
+ if ((res = (URES *)GetResWithName(R_COLLECTOR, res_all.res_collector.hdr.name)) == NULL) {
+ Mmsg(config->m_errmsg, _("Cannot find Statistics resource %s\n"), res_all.res_collector.hdr.name);
+ return false;
+ }
+ res->res_collector.metrics = res_all.res_collector.metrics;
+ // Dmsg2(100, "metrics = 0x%p 0x%p\n", res->res_collector.metrics, res_all.res_collector.metrics);
+ break;
default:
Emsg1(M_ERROR, 0, _("Unknown resource type %d\n"), type);
error = 1;
break;
}
- /* Note, the resoure name was already saved during pass 1,
+ /* Note, the resource name was already saved during pass 1,
* so here, we can just release it.
*/
if (res_all.res_dir.hdr.name) {
case R_MSGS:
size = sizeof(MSGS);
break;
+ case R_COLLECTOR:
+ size = sizeof(COLLECTOR);
+ break;
default:
printf(_("Unknown resource type %d\n"), type);
error = 1;
/*
* Resource codes -- they must be sequential for indexing
+ * TODO: Check if we can change R_codes to enum like for other daemons.
*/
#define R_FIRST 1001
#define R_CLIENT 1002
#define R_MSGS 1003
#define R_CONSOLE 1004
+#define R_COLLECTOR 1005
-#define R_LAST R_CONSOLE
+#define R_LAST R_COLLECTOR
/*
* Some resource attributes
* resource structure definitions.
*/
union URES {
- DIRRES res_dir;
- CLIENT res_client;
- MSGS res_msgs;
- CONSRES res_cons;
- RES hdr;
+ DIRRES res_dir;
+ CLIENT res_client;
+ MSGS res_msgs;
+ CONSRES res_cons;
+ RES hdr;
+ COLLECTOR res_collector;
};
extern int status_cmd(JCR *jcr);
extern int qstatus_cmd(JCR *jcr);
extern int accurate_cmd(JCR *jcr);
+extern int collect_cmd(JCR *jcr);
/* Forward referenced functions */
static int backup_cmd(JCR *jcr);
static int storage_cmd(JCR *jcr);
static int session_cmd(JCR *jcr);
static int response(JCR *jcr, BSOCK *sd, char *resp, const char *cmd);
-static void filed_free_jcr(JCR *jcr);
static int open_sd_read_session(JCR *jcr);
static int runscript_cmd(JCR *jcr);
static int runbefore_cmd(JCR *jcr);
{"stop", cancel_cmd, ACCESS_REMOTE},
{"proxy", proxy_cmd, ACCESS_REMOTE},
{"testnetwork", fd_testnetwork_cmd, 0},
+ {"statistics", collect_cmd, 0},
#ifdef DEVELOPER
{"exit", exit_cmd, 0},
#endif
}
first = false;
dir->msg[dir->msglen] = 0;
- Dmsg1(100, "<dird: %s", dir->msg);
+ Dmsg1(100, "<dird: %s\n", dir->msg);
found = false;
for (i=0; cmds[i].cmd; i++) {
if (strncmp(cmds[i].cmd, dir->msg, strlen(cmds[i].cmd)) == 0) {
* Destroy the Job Control Record and associated
* resources (sockets).
*/
-static void filed_free_jcr(JCR *jcr)
+void filed_free_jcr(JCR *jcr)
{
if (jcr->dir_bsock) {
free_bsock(jcr->dir_bsock);
int add_wild_to_fileset(JCR *jcr, const char *item, int type);
int add_regex_to_fileset(JCR *jcr, const char *item, int type);
findINCEXE *new_include(JCR *jcr);
+void filed_free_jcr(JCR *jcr);
/* from snapshot.c */
int snapshot_cmd(JCR *jcr);
VSSClient *VSSInit();
#endif
+/* from fdcollect.c */
+bool update_permanent_stats(void *data);
+void initialize_statcollector();
+void start_collector_threads();
+void terminate_collector_threads();
+
/* Definition for encyption cipher/digest type */
void store_cipher_type(LEX *lc, RES_ITEM *item, int index, int pass);
void store_digest_type(LEX *lc, RES_ITEM *item, int index, int pass);
/* Forward referenced functions */
static void list_running_jobs(STATUS_PKT *sp);
static void list_status_header(STATUS_PKT *sp);
+static void list_collectors_status(STATUS_PKT *sp, char *collname);
+static void api_collectors_status(STATUS_PKT *sp, char *collname);
/* Static variables */
static char qstatus1[] = ".status %127s\n";
}
#endif
bstrftime_nc(dt, sizeof(dt), njcr->start_time);
+ if (!njcr->director){
+ /* skip any internal system jobs */
+ continue;
+ }
if (njcr->JobId == 0) {
len = Mmsg(msg, _("Director connected %sat: %s\n"),
(njcr->dir_bsock && njcr->dir_bsock->tls)?_("using TLS "):"",
BSOCK *dir = jcr->dir_bsock;
POOLMEM *cmd;
JCR *njcr;
+ char *collname;
s_last_job* job;
STATUS_PKT sp;
}
unbash_spaces(cmd);
+ collname = strchr(cmd, '=');
+ if (collname){
+ *collname = 0;
+ collname++;
+ }
if (strcasecmp(cmd, "current") == 0) {
dir->fsend(OKqstatus, cmd);
foreach_jcr(njcr) {
} else if (strcasecmp(cmd, "terminated") == 0) {
sp.api = MAX(sp.api, 1);
list_terminated_jobs(&sp); /* defined in lib/status.h */
+ } else if (strcasecmp(cmd, "statistics") == 0) {
+ sp.api = MAX(sp.api, 1);
+ list_collectors_status(&sp, collname);
} else {
pm_strcpy(&jcr->errmsg, dir->msg);
Jmsg1(jcr, M_FATAL, 0, _("Bad .status command: %s\n"), jcr->errmsg);
free_memory(cmd);
return 1;
}
+
+static void list_collectors_status(STATUS_PKT *sp, char *collname)
+{
+ URES *res;
+ int len;
+ POOL_MEM buf(PM_MESSAGE);
+
+ Dmsg2(200, "enter list_collectors_status() api=%i coll=%s\n", sp->api, NPRTB(collname));
+ if (sp->api > 1) {
+ api_collectors_status(sp, collname);
+ return;
+ }
+
+ LockRes();
+ foreach_res(res, R_COLLECTOR) {
+ if (collname && !bstrcmp(collname, res->res_collector.hdr.name)){
+ continue;
+ }
+ Dmsg1(500, "processing: %s\n", res->res_collector.hdr.name);
+ len = render_collector_status(res->res_collector, buf);
+ sendit(buf.c_str(), len, sp);
+ };
+ UnlockRes();
+ if (!collname){
+ len = render_updcollector_status(buf);
+ sendit(buf.c_str(), len, sp);
+ }
+ Dmsg0(200, "leave list_collectors_status()\n");
+}
+
+static void api_collectors_status(STATUS_PKT *sp, char *collname)
+{
+ URES *res;
+ OutputWriter ow(sp->api_opts);
+ POOLMEM *buf;
+
+ Dmsg1(200, "enter api_collectors_status() %s\n", NPRTB(collname));
+ ow.start_group("collector_backends");
+ LockRes();
+ foreach_res(res, R_COLLECTOR) {
+ if (collname && !bstrcmp(collname, res->res_collector.hdr.name)){
+ continue;
+ }
+ Dmsg1(500, "processing: %s\n", res->res_collector.hdr.name);
+ api_render_collector_status(res->res_collector, ow);
+ };
+ UnlockRes();
+ buf = ow.end_group();
+ if (!collname){
+ ow.start_group("collector_update");
+ api_render_updcollector_status(ow);
+ buf = ow.end_group();
+ }
+ sendit(buf, strlen(buf), sp);
+ Dmsg0(200, "leave api_collectors_status()\n");
+};
../config.h ../jcr.h ../version.h \
address_conf.h alist.h attr.h base64.h bsockcore.h \
berrno.h bits.h bjson.h bpipe.h breg.h bregex.h \
- bsock.h btime.h btimers.h crypto.h dlist.h \
+ bsock.h bstat.h btime.h btimers.h crypto.h dlist.h \
flist.h fnmatch.h guid_to_name.h htable.h lex.h \
lib.h lz4.h md5.h mem_pool.h message.h \
openssl.h parse_conf.h plugins.h protos.h queue.h rblist.h \
runscript.h rwlock.h serial.h sellist.h sha1.h sha2.h \
smartall.h status.h tls.h tree.h var.h \
- watchdog.h workq.h ini.h \
+ watchdog.h workq.h ini.h collect.h \
lockmgr.h devlock.h output.h bwlimit.h
#
# libbac
#
LIBBAC_SRCS = attr.c base64.c berrno.c bsys.c binflate.c bget_msg.c \
- bnet.c bnet_server.c bsock.c bpipe.c bsnprintf.c btime.c \
+ bnet.c bnet_server.c bsock.c bpipe.c bsnprintf.c bstat.c btime.c \
cram-md5.c crc32.c crypto.c daemon.c edit.c fnmatch.c \
guid_to_name.c hmac.c jcr.c lex.c lz4.c alist.c dlist.c \
md5.c message.c mem_pool.c openssl.c \
runscript.c rwlock.c scan.c sellist.c serial.c sha1.c sha2.c \
signal.c smartall.c rblist.c tls.c tree.c \
util.c var.c watchdog.c workq.c btimers.c \
- worker.c flist.c \
+ worker.c flist.c bcollector.c collect.c \
address_conf.c breg.c htable.c lockmgr.c devlock.c output.c bwlimit.c
LIBBAC_OBJS = $(LIBBAC_SRCS:.c=.o)
$(RMF) ini.o
$(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) ini.c
+bstat_test: Makefile libbac.la bstat.c unittests.o
+ $(RMF) bstat.o
+ $(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) bstat.c
+ $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -L. -o $@ bstat.o unittests.o $(DLIB) -lbac -lm $(LIBS)
+ $(RMF) bstat.o
+ $(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) bstat.c
+
bsockcore_test: Makefile libbac.la bsockcore.c unittests.o
$(RMF) bsockcore.o
$(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) bsockcore.c
$(RMF) bsock.o
$(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) bsock.c
+collect_test: Makefile libbac.la collect.c unittests.o
+ $(RMF) collect.o
+ $(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) collect.c
+ $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -L. -o $@ collect.o unittests.o $(DLIB) -lbac -lm $(LIBS)
+ $(RMF) collect.o
+ $(CXX) $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) collect.c
+
install-includes:
$(MKDIR) $(DESTDIR)/$(includedir)/bacula
for I in $(INCLUDE_FILES); do \
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2016 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Radosław Korzeniewski, MMXVIII
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * This is a Bacula statistics internal collector thread.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#include "bacula.h"
+#include "parse_conf.h"
+#include "jcr.h"
+#include <time.h>
+
+#define SPOOLFNAME "%s/%s.collector.%s.spool"
+
+/* this is a single and global update collector class */
+static UPDATECOLLECTOR updcollector;
+
+/*
+ * COLLECTOR resource class lock.
+ */
+void COLLECTOR::lock()
+{
+ pthread_mutex_lock(&mutex);
+};
+
+/*
+ * COLLECTOR resource class unlock.
+ */
+void COLLECTOR::unlock()
+{
+ pthread_mutex_unlock(&mutex);
+};
+
+/*
+ * Sets a spooled flags for collector.
+ */
+void COLLECTOR::setspooled(int s)
+{
+ lock();
+ spooled = s;
+ unlock();
+};
+
+/*
+ * Gets a spooled flags from collector.
+ */
+int COLLECTOR::getspooled()
+{
+ int s;
+
+ lock();
+ s = spooled;
+ unlock();
+ return s;
+};
+
+/*
+ * Updates a collector timestamp.
+ */
+void COLLECTOR::updatetimestamp()
+{
+ lock();
+ timestamp = time(NULL);
+ unlock();
+};
+
+/*
+ * Render a CSV representation of the metric and writes them to opened file.
+ *
+ * in:
+ * fd - opened file descriptor used for write
+ * collector - the Statistics resource class
+ * item - metric to render and write
+ * timestamp - the current Unix time used for metric rendering
+ * out:
+ * len - the length of rendered and data written
+ */
+int write_metricascsv(int fd, COLLECTOR *collector, bstatmetric *item, time_t timestamp)
+{
+ int len;
+ int status;
+ POOL_MEM met(PM_NAME);
+ POOL_MEM buf(PM_MESSAGE);
+ POOL_MEM out(PM_MESSAGE);
+
+ render_metric_prefix(collector, met, item);
+ item->render_metric_value(buf);
+ Mmsg(out, "%lld, %s, %s\n", timestamp, met.c_str(), buf.c_str());
+ len = strlen(out.c_str());
+ status = write(fd, out.c_str(), len);
+ return status == len;
+};
+
+/*
+ * Render a Graphite representation of the metric and store in the buffer.
+ *
+ * in:
+ * collector - the Statistics resource class
+ * out - the POLL_MEM buffer to render a metric to
+ * item - metric to render and write
+ * timestamp - the current Unix time used for metric rendering
+ */
+void render_metric_graphite(COLLECTOR *collector, POOL_MEM &out, bstatmetric *item, time_t timestamp)
+{
+ POOL_MEM tmp1(PM_NAME);
+ POOL_MEM tmp2(PM_NAME);
+
+ render_metric_prefix(collector, tmp1, item);
+ item->render_metric_value(tmp2);
+ Mmsg(out, "%s %s %lld\n", tmp1.c_str(), tmp2.c_str(), timestamp);
+};
+
+/*
+ * Saves a metrics grouped at an array list to file pointed by a File parameter from Statistics
+ * resource as CSV data.
+ *
+ * in:
+ * collector - the Statistics resource class
+ * list - an array list of metrics to save to file
+ * timestamp - the current Unix time used for metric rendering
+ * out:
+ * True when the Statistics should continue to work (i.e. a save was successful)
+ * False when the Statistics should finish the work because some fatal error occurred which
+ * prevents the Statistics from work
+ */
+bool save_metrics2csv(COLLECTOR *collector, alist *list)
+{
+ int fd;
+ bstatmetric *item;
+
+ fd = open(collector->file, O_WRONLY|O_CREAT|O_APPEND, 0640);
+ if (fd > 0){
+ /* open success */
+ foreach_alist(item, list){
+ if (!write_metricascsv(fd, collector, item, collector->timestamp)){
+ berrno be;
+ Emsg2(M_ERROR, 0, "Error saving data file: %s Err=%s\n", collector->file, be.bstrerror());
+ collector->lock();
+ Mmsg(collector->errmsg, "Error saving data file: %s Err=%s", collector->file, be.bstrerror());
+ collector->unlock();
+ close(fd);
+ return false;
+ }
+ }
+ close(fd);
+ return true;
+ }
+ /* error */
+ berrno be;
+ Emsg2(M_ERROR, 0, "Error opening collector data file: %s Err=%s\n", collector->file, be.bstrerror());
+ collector->lock();
+ Mmsg(collector->errmsg, "Error opening collector data file: %s Err=%s", collector->file, be.bstrerror());
+ collector->unlock();
+ return false;
+};
+
+/*
+ * Saves a metrics grouped at an array list to Graphite pointed by a Statistics parameters.
+ * The backend connects to a Graphite server and push all metrics data from metrics array list.
+ * When connection to Graphite server is unsuccessful then a backend will spool metrics data
+ * automatically into a local file located at WorkingDirectory of the daemon. A spooling to file
+ * continue until connection will be restored again. The spooling file is preserved between any
+ * collector thread or daemon restarts.
+ *
+ * in:
+ * collector - the Statistics resource class
+ * list - an array list of metrics to save to file
+ * timestamp - the current Unix time used for metric rendering
+ * spooled - a variable which saves a backend spooling status between calling
+ * out:
+ * True when the Statistics should continue to work (i.e. a save was successful)
+ * False when the Statistics should finish the work because some fatal error occurred which
+ * prevents the Statistics from work
+ */
+bool save_metrics2graphite(COLLECTOR *collector, alist *list)
+{
+ BSOCKCORE *bs;
+ int fd;
+ int len;
+ int spooled;
+ bstatmetric *item;
+ POOL_MEM buf(PM_MESSAGE);
+ POOL_MEM sname(PM_FNAME);
+
+ bs = New(BSOCKCORE);
+ if (bs->connect(collector->jcr, 1, 3, 0, collector->hdr.name, collector->host, NULL, collector->port, 0)) {
+ /* connect success, despool data if already spooled */
+ collector->errmsg[0] = 0;
+ spooled = collector->getspooled();
+ if (spooled != BCOLLECT_SPOOL_NO && collector->spool_directory){
+ collector->setspooled(BCOLLECT_SPOOL_DESPOOL);
+ Mmsg(sname, SPOOLFNAME, collector->spool_directory, collector->daemon, collector->hdr.name);
+ fd = open(sname.c_str(), O_RDONLY);
+ if (fd > 0){
+ /* we have some spooled data */
+ Dmsg2(500, "%s despooling metrics from: %s\n", collector->hdr.name, sname.c_str());
+ while ((len = read(fd, bs->msg, sizeof_pool_memory(bs->msg))) > 0){
+ bs->msglen = len;
+ bs->send();
+ }
+ close(fd);
+ unlink(sname.c_str());
+ }
+ }
+ bs->msg[0] = 0;
+ /* render and send metrics to Graphite */
+ foreach_alist(item, list){
+ render_metric_graphite(collector, buf, item, collector->timestamp);
+ pm_strcat(bs->msg, buf);
+ }
+ bs->msglen = strlen(bs->msg);
+ bs->send();
+ bs->close();
+ collector->setspooled(BCOLLECT_SPOOL_NO);
+ } else {
+ /* error connecting */
+ berrno be;
+ collector->lock();
+ /* TODO: I'm using the errno set by bsockcore, but it return "Interrupted system call"
+ * instead of a proper error number. We have to consider a better solution. */
+ Mmsg(collector->errmsg, "Could not connect to %s:%d Err=%s", collector->host, collector->port, be.bstrerror());
+ collector->unlock();
+ /* spool data if requested */
+ if (collector->spool_directory){
+ /* spooling only when spool_directory defined */
+ Mmsg(sname, SPOOLFNAME, collector->spool_directory, collector->daemon, collector->hdr.name);
+ fd = open(sname.c_str(), O_WRONLY|O_CREAT|O_APPEND, 0640);
+ if (fd > 0){
+ /* open success */
+ Dmsg2(500, "%s spooling metrics to: %s\n", collector->hdr.name, sname.c_str());
+ foreach_alist(item, list){
+ render_metric_graphite(collector, buf, item, collector->timestamp);
+ len = strlen(buf.c_str());
+ if (write(fd, buf.c_str(), len) != len){
+ berrno be;
+ Emsg2(M_ERROR, 0, "Error saving spool file: %s Err=%s\n", collector->file, be.bstrerror());
+ Emsg1(M_ERROR, 0, "Statistics spooling for %s disabled.\n", collector->hdr.name);
+ collector->lock();
+ collector->spool_directory = NULL;
+ Mmsg(collector->errmsg, "Error saving spool file: %s Err=%s", collector->file, be.bstrerror());
+ collector->unlock();
+ close(fd);
+ delete(bs);
+ return true;
+ }
+ }
+ close(fd);
+ collector->setspooled(BCOLLECT_SPOOL_YES);
+ } else {
+ /* error */
+ berrno be;
+ Emsg2(M_ERROR, 0, "Error opening collector spool file: %s Err=%s\n", sname.c_str(), be.bstrerror());
+ Emsg1(M_ERROR, 0, "Statistics spooling for %s disabled.\n", collector->hdr.name);
+ collector->lock();
+ collector->spool_directory = NULL;
+ Mmsg(collector->errmsg, "Error opening collector spool file: %s Err=%s", sname.c_str(), be.bstrerror());
+ collector->unlock();
+ }
+ }
+ }
+
+ delete (bs);
+ return true;
+};
+
+/*
+ * The main Statistics backend thread function.
+ */
+extern "C"
+void *collector_thread(void *arg)
+{
+ COLLECTOR *collector;
+ alist *data = NULL;
+ bstatmetric *item;
+ char *filter;
+ char *fltm;
+ alist *filtered = NULL;
+ bool oper, toappend, prevmatch;
+ int match;
+ bool status = true;
+
+ collector = (COLLECTOR*)arg;
+ collector->lock();
+ collector->valid = true;
+ collector->running = true;
+ collector->spooled = BCOLLECT_SPOOL_UNK; /* when thread start we do not know if spooled */
+ switch (collector->type){
+ case COLLECTOR_BACKEND_CSV:
+ /* no spooling for CSV collector */
+ collector->spool_directory = NULL;
+ break;
+ }
+ collector->errmsg = get_pool_memory(PM_MESSAGE);
+ collector->errmsg[0] = 0;
+ collector->unlock();
+
+ while (status){
+ collector->lock();
+ if (!collector->valid){
+ /* exit thread */
+ collector->unlock();
+ Dmsg1(100, "Statistics \"%s\" exited on request.\n", collector->name());
+ goto cleanup;
+ }
+ collector->unlock();
+ /* grab data from bstatcollect */
+ data = collector->statcollector->get_all();
+ collector->updatetimestamp();
+ if (data){
+ /* we have some data to proceed */
+ if (collector->metrics){
+ /* have some metrics to filter */
+ filtered = New(alist(100, not_owned_by_alist));
+ /* iterate trough all metrics to filter it out */
+ foreach_alist(item, data){
+ Dmsg1(1500, "processing: %s\n", item->name);
+ toappend = true;
+ prevmatch = false;
+ foreach_alist(filter, collector->metrics){
+ fltm = filter;
+ oper = false; // add filtered metric
+ if (filter[0] == '!'){
+ fltm = filter + 1;
+ oper = true; // remove filtered metric
+ }
+ match = fnmatch(fltm, item->name, 0);
+ /* now we have to decide if metric should be filtered or not */
+ toappend = (!oper && match == 0) || (match !=0 && prevmatch);
+ prevmatch = match == 0;
+ }
+ if (toappend){
+ /* found */
+ Dmsg0(1500, "metric append\n");
+ filtered->append(item);
+ }
+ }
+ } else {
+ filtered = data;
+ }
+ Dmsg1(1000, "collected metrics: %d\n", filtered->size());
+ /* save data to destination */
+ switch (collector->type){
+ case COLLECTOR_BACKEND_CSV:
+ status = save_metrics2csv(collector, filtered);
+ break;
+ case COLLECTOR_BACKEND_Graphite:
+ status = save_metrics2graphite(collector, filtered);
+ break;
+ }
+ /* free data */
+ if (filtered != data){
+ delete(filtered);
+ }
+ free_metric_alist(data);
+ data = NULL;
+ filtered = NULL;
+ }
+ if (status){
+ /* wait $Interval seconds*/
+ Dmsg1(2000, "collector sleep (%d secs)\n", collector->interval);
+ bmicrosleep(collector->interval, 0);
+ }
+ }
+
+ Dmsg1(100, "Statistics \"%s\" exited.\n", collector->name());
+cleanup:
+ collector->lock();
+ collector->running = false;
+ free_jcr(collector->jcr);
+ collector->unlock();
+ return NULL;
+};
+
+/*
+ * The main Statistics update thread function.
+ */
+extern "C"
+void *updatecollector_thread(void *arg)
+{
+ bool status = true;
+
+ updcollector.lock();
+ if (updcollector.routine == NULL || updcollector.jcr == NULL || updcollector.interval == 0){
+ /* cannot execute thread when uninitialized */
+ updcollector.unlock();
+ Dmsg0(100, "Update Statistics uninitialized!\n");
+ return NULL;
+ }
+ updcollector.running = true;
+ updcollector.valid = true;
+ updcollector.unlock();
+ /* main thread loop */
+ while (status){
+ updcollector.lock();
+ if (!updcollector.valid){
+ /* exit thread */
+ updcollector.unlock();
+ Dmsg0(100, "Update Statistics exited on request.\n");
+ goto updcleanup;
+ }
+ updcollector.lastupdate = time(NULL);
+ updcollector.unlock();
+ status = updcollector.routine(updcollector.data);
+ if (status){
+ /* wait $Interval seconds*/
+ Dmsg1(2000, "updcollector sleep (%d secs)\n", updcollector.interval);
+ bmicrosleep(updcollector.interval, 0);
+ }
+ }
+ Dmsg0(100, "Update Statistics exited.\n");
+updcleanup:
+ updcollector.lock();
+ updcollector.running = false;
+ updcollector.interval = 0;
+ free_jcr(updcollector.jcr);
+ updcollector.unlock();
+ return NULL;
+};
+
+/*
+ * Starts a single collector backend thread.
+ * If collector thread start will be unsuccessful then it will abort Bacula.
+ *
+ * in:
+ * collector - a collector resource used for thread start and initialization
+ */
+void start_collector_thread(COLLECTOR *collector)
+{
+ int status;
+
+ Dmsg1(100, "start_collector_thread: %p\n", collector);
+ /* initialize collector res for thread */
+ pthread_mutex_init(&collector->mutex, NULL);
+
+ /* create thread */
+ if ((status = pthread_create(&collector->thid, NULL, collector_thread, (void *)collector)) != 0) {
+ berrno be;
+ Emsg1(M_ABORT, 0, _("Cannot create Statistics thread: %s\n"), be.bstrerror(status));
+ }
+ return;
+};
+
+/*
+ * Stops a collector backend thread.
+ */
+void stop_collector_thread(COLLECTOR *collector)
+{
+ char *elt;
+
+ Dmsg2(500, "valid=%d metrics=%p\n", collector->valid, collector->metrics);
+ if (collector->metrics) {
+ foreach_alist(elt, collector->metrics) {
+ Dmsg1(100, "str=%s\n", elt);
+ }
+ }
+ collector->lock();
+ collector->valid = false;
+ pthread_kill(collector->thid, SIGUSR2);
+ collector->unlock();
+ /* wait for thread end */
+ pthread_join(collector->thid, NULL);
+};
+
+/*
+ * Starts a single update collector thread.
+ * If collector thread start will be unsuccessful then it will abort Bacula.
+ *
+ * in:
+ * initdata - the update collector initialization data
+ */
+void start_updcollector_thread(UPDATE_COLLECTOR_INIT_t &initdata)
+{
+ int status;
+
+ Dmsg0(100, "start_updcollector_thread\n");
+ updcollector.interval = initdata.interval;
+ updcollector.routine = initdata.routine;
+ updcollector.data = initdata.data;
+ updcollector.jcr = initdata.jcr;
+ /* create thread */
+ if ((status = pthread_create(&updcollector.thid, NULL, &updatecollector_thread, NULL)) != 0) {
+ berrno be;
+ Emsg1(M_ABORT, 0, _("Cannot create Update Statistics thread: %s\n"), be.bstrerror(status));
+ }
+ return;
+};
+
+/*
+ * Stops a collector update thread.
+ */
+void stop_updcollector_thread()
+{
+ updcollector.lock();
+ updcollector.valid = false;
+ pthread_kill(updcollector.thid, SIGUSR2);
+ updcollector.unlock();
+ /* wait for thread end */
+ pthread_join(updcollector.thid, NULL);
+};
+
+/*
+ * A support function used in resource's show/dump functions.
+ */
+void dump_collector_resource(COLLECTOR &res_collector, void sendit(void *sock, const char *fmt, ...), void *sock)
+{
+ char *metric;
+
+ sendit(sock, _("Statistics: name=%s\n"), res_collector.hdr.name);
+ sendit(sock, _(" type=%d interval=%ld secs\n"), res_collector.type, res_collector.interval);
+ sendit(sock, _(" prefix=%s\n"), res_collector.prefix ? res_collector.prefix : "");
+ switch (res_collector.type){
+ case COLLECTOR_BACKEND_CSV:
+ sendit(sock, _(" file=%s\n"), res_collector.file ? res_collector.file : "<empty>");
+ break;
+ case COLLECTOR_BACKEND_Graphite:
+ sendit(sock, _(" host=%s port=%d\n"),
+ res_collector.host ? res_collector.host : "localhost",
+ res_collector.port);
+ break;
+ }
+ if (res_collector.metrics){
+ foreach_alist(metric, res_collector.metrics){
+ sendit(sock, _(" metric=%s\n"), metric);
+ };
+ }
+};
+
+/*
+ * A support function frees a COLLECTOR class resources.
+ */
+void free_collector_resource(COLLECTOR &res_collector)
+{
+ if (res_collector.file){
+ free(res_collector.file);
+ }
+ if (res_collector.host){
+ free(res_collector.host);
+ }
+ if (res_collector.prefix){
+ free(res_collector.prefix);
+ }
+ if (res_collector.errmsg){
+ free_pool_memory(res_collector.errmsg);
+ }
+ if (res_collector.metrics){
+ delete res_collector.metrics;
+ }
+ pthread_mutex_destroy(&res_collector.mutex);
+};
+
+/*
+ * UPDATECOLLECTOR class lock.
+ */
+void UPDATECOLLECTOR::lock()
+{
+ pthread_mutex_lock(&mutex);
+};
+
+/*
+ * UPDATECOLLECTOR class unlock.
+ */
+void UPDATECOLLECTOR::unlock()
+{
+ pthread_mutex_unlock(&mutex);
+};
+
+/*
+ * UPDATECOLLECTOR constructor.
+ */
+UPDATECOLLECTOR::UPDATECOLLECTOR()
+{
+ running = false;
+ valid = false;
+ routine = NULL;
+ data = NULL;
+ jcr = NULL;
+ interval = 0;
+ lastupdate = 0;
+ memset(&thid, 0, sizeof(thid));
+ pthread_mutex_init(&mutex, NULL);
+};
+
+/*
+ * UPDATECOLLECTOR destructor.
+ */
+UPDATECOLLECTOR::~UPDATECOLLECTOR()
+{
+ pthread_mutex_destroy(&mutex);
+};
+
+const char *str_updcollector_status()
+{
+ const char* status;
+
+ if (updcollector.valid){
+ status = updcollector.running?"running":"stopped";
+ } else {
+ status = updcollector.running?"waiting to exit":"stopped";
+ }
+ return status;
+};
+
+/*
+ * A support function renders an Update Statistics thread status into a buffer.
+ *
+ * in:
+ * buf - a POLL_MEM buffer to render into
+ * out:
+ * the length of rendered text
+ */
+int render_updcollector_status(POOL_MEM &buf)
+{
+ int len;
+ char dt[MAX_TIME_LENGTH];
+ const char *status;
+ utime_t t;
+ utime_t i;
+
+ updcollector.lock();
+ status = str_updcollector_status();
+ t = updcollector.lastupdate;
+ i = updcollector.interval;
+ updcollector.unlock();
+ bstrftime_nc(dt, sizeof(dt), t);
+ len = Mmsg(buf, "Update Statistics: %s interval=%d secs lastupdate=%s\n\n",
+ status, i, dt);
+ return len;
+};
+
+/*
+ * A support function renders an Update Statistics thread status into an OutputWriter for APIv2.
+ *
+ * in:
+ * ow - OutputWriter for apiv2
+ * out:
+ * rendered status in OutputWritter buffer
+ */
+void api_render_updcollector_status(OutputWriter &ow)
+{
+ const char *status;
+ time_t t;
+ utime_t i;
+
+ updcollector.lock();
+ status = str_updcollector_status();
+ t = updcollector.lastupdate;
+ i = updcollector.interval;
+ updcollector.unlock();
+ ow.get_output(
+ OT_START_OBJ,
+ OT_STRING, "status", status,
+ OT_INT, "interval", i,
+ OT_UTIME, "lasttimestamp", t,
+ OT_END_OBJ,
+ OT_END
+ );
+ return;
+};
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2016 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Radosław Korzeniewski, MMXVIII
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * This is a Bacula statistics internal collector thread.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#ifndef __BCOLLECTOR_H_
+#define __BCOLLECTOR_H_
+
+/* Supported backend types */
+enum {
+ COLLECTOR_BACKEND_Undef = 0,
+ COLLECTOR_BACKEND_CSV,
+ COLLECTOR_BACKEND_Graphite,
+};
+
+/* spooling status for supported backends */
+enum {
+ BCOLLECT_SPOOL_UNK,
+ BCOLLECT_SPOOL_YES,
+ BCOLLECT_SPOOL_NO,
+ BCOLLECT_SPOOL_DESPOOL,
+};
+
+/* forward reference only */
+class COLLECTOR;
+class OutputWriter;
+
+class UPDATECOLLECTOR: public SMARTALLOC {
+public:
+ utime_t interval; /* interval in seconds between metrics collection */
+ utime_t lastupdate; /* when last update */
+ pthread_t thid; /* thread id for collector thread */
+ pthread_mutex_t mutex; /* when accessing collector resource data you should lock it first */
+ bool valid; /* when set to false the collector thread should involuntary exit */
+ bool running; /* show if the background update thread is running */
+ void *data; /* data parameter for thread routine */
+ bool (*routine)(void *data); /* routine for update collector thread */
+ JCR *jcr;
+public:
+ /* Methods */
+ char *name() const;
+ void lock();
+ void unlock();
+ UPDATECOLLECTOR();
+ ~UPDATECOLLECTOR();
+};
+
+typedef struct {
+ JCR *jcr;
+ utime_t interval;
+ void *data;
+ bool (*routine)(void *data);
+} UPDATE_COLLECTOR_INIT_t;
+
+void start_collector_thread(COLLECTOR *collector);
+void stop_collector_thread(COLLECTOR *collector);
+void start_updcollector_thread(UPDATE_COLLECTOR_INIT_t &initdata);
+void stop_updcollector_thread();
+void dump_collector_resource(COLLECTOR &res_collector, void sendit(void *sock, const char *fmt, ...), void *sock);
+void free_collector_resource(COLLECTOR &res_collector);
+int render_updcollector_status(POOL_MEM &buf);
+void api_render_updcollector_status(OutputWriter &ow);
+
+#endif /* __BCOLLECTOR_H_ */
#include "lib/breg.h"
extern s_kw msg_types[];
+extern s_collt collectortypes[];
extern RES_TABLE resources[];
union URES {
}
sendit(NULL, "]");
}
+
+void display_collector_types(HPKT &hpkt)
+{
+ int i;
+ for (i=0; collectortypes[i].type_name; i++) {
+ if (*(int32_t *)(hpkt.ritem->value) == collectortypes[i].coll_type) {
+ sendit(NULL, "\n \"%s\": %s", hpkt.ritem->name,
+ quote_string(hpkt.edbuf, collectortypes[i].type_name));
+ return;
+ }
+ }
+}
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2016 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Radosław Korzeniewski, MMXVIII
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * This is a Bacula statistics internal collector.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#include "bstat.h"
+
+// #define TEST_PROGRAM_DEBUG
+
+/*
+ * Default bstatmetric constructor
+ */
+bstatmetric::bstatmetric()
+{
+#ifdef TEST_PROGRAM_DEBUG
+ Pmsg1(0, "bstatmetric::bstatmetric(): %p\n", this);
+#endif
+ init(NULL, METRIC_UNDEF, METRIC_UNIT_EMPTY, NULL);
+};
+
+/*
+ * This is a special initialization constructor. No value will be set.
+ */
+bstatmetric::bstatmetric(char *mname, metric_type_t mtype, metric_unit_t munit, char *descr)
+{
+#ifdef TEST_PROGRAM_DEBUG
+ Pmsg1(0, "bstatmetric::bstatmetric(type/unit): %p\n", this);
+#endif
+ init(mname, mtype, munit, descr);
+};
+
+/*
+ * This is a special initialization constructor for Boolean metric with a value.
+ */
+bstatmetric::bstatmetric(char *mname, metric_unit_t munit, bool mvalue, char *descr)
+{
+#ifdef TEST_PROGRAM_DEBUG
+ Pmsg1(0, "bstatmetric::bstatmetric(bool): %p\n", this);
+#endif
+ init(mname, METRIC_BOOL, munit, descr);
+ value.boolval = mvalue;
+};
+
+/*
+ * This is a special initialization constructor for Integer metric with a value.
+ */
+bstatmetric::bstatmetric(char *mname, metric_unit_t munit, int64_t mvalue, char *descr)
+{
+#ifdef TEST_PROGRAM_DEBUG
+ Pmsg1(0, "bstatmetric::bstatmetric(int64): %p\n", this);
+#endif
+ init(mname, METRIC_INT, munit, descr);
+ value.int64val = mvalue;
+};
+
+/*
+ * This is a special initialization constructor for Floating point metric with a value.
+ */
+bstatmetric::bstatmetric(char *mname, metric_unit_t munit, float mvalue, char *descr)
+{
+#ifdef TEST_PROGRAM_DEBUG
+ Pmsg1(0, "bstatmetric::bstatmetric(float): %p\n", this);
+#endif
+ init(mname, METRIC_FLOAT, munit, descr);
+ value.floatval = mvalue;
+};
+
+/*
+ * The standard "copy" operator.
+ * Copies all values from source metric to this destination metric.
+ */
+bstatmetric& bstatmetric::operator=(const bstatmetric& orig)
+{
+#ifdef TEST_PROGRAM_DEBUG
+ Pmsg1(0, "bstatmetric& bstatmetric::operator=(const bstatmetric& orig): %p\n", this);
+#endif
+ if (name){
+ free(name);
+ }
+ if (description){
+ free(description);
+ }
+ init(orig.name, orig.type, orig.unit, orig.description);
+ switch (type){
+ case METRIC_BOOL:
+ value.boolval = orig.value.boolval;
+ break;
+ case METRIC_INT:
+ value.int64val = orig.value.int64val;
+ break;
+ case METRIC_FLOAT:
+ value.floatval = orig.value.floatval;
+ break;
+ default:
+ value.int64val = 0;
+ }
+ return *this;
+};
+
+/*
+ * Default destructor. Frees all allocated resources.
+ */
+bstatmetric::~bstatmetric()
+{
+#ifdef TEST_PROGRAM_DEBUG
+ Pmsg1(0, "bstatmetric::~bstatmetric(): %p\n", this);
+#endif
+ if (name){
+ free(name);
+ }
+ if (description){
+ free(description);
+ }
+};
+
+/*
+ * Class initialization method used in constructors.
+ */
+void bstatmetric::init(char* mname, metric_type_t mtype, metric_unit_t munit, char *descr)
+{
+ name = mname ? bstrdup(mname) : NULL;
+ type = mtype;
+ unit = munit;
+ value.int64val = 0;
+ description = descr ? bstrdup(descr) : NULL;
+};
+
+/*
+ * Return a string representation of a metric type.
+ */
+const char *bstatmetric::metric_type_str()
+{
+ switch (type){
+ case METRIC_INT:
+ return "Integer";
+ case METRIC_BOOL:
+ return "Boolean";
+ case METRIC_FLOAT:
+ return "Float";
+ default:
+ return "Undefined";
+ }
+};
+
+/*
+ * Return a string representation of a metric unit.
+ * Note! Update the method below when add new unit type.
+ */
+const char *bstatmetric::metric_unit_str()
+{
+ switch (unit){
+ case METRIC_UNIT_BYTE:
+ return "Bytes";
+ case METRIC_UNIT_BYTESEC:
+ return "Bytes/Sec";
+ case METRIC_UNIT_JOB:
+ return "Jobs";
+ case METRIC_UNIT_CLIENT:
+ return "Clients";
+ case METRIC_UNIT_FILESET:
+ return "FileSets";
+ case METRIC_UNIT_POOL:
+ return "Pools";
+ case METRIC_UNIT_STORAGE:
+ return "Storages";
+ case METRIC_UNIT_SCHEDULE:
+ return "Schedules";
+ case METRIC_UNIT_FILE:
+ return "Files";
+ case METRIC_UNIT_VOLUME:
+ return "Volumes";
+ case METRIC_UNIT_SEC:
+ return "Seconds";
+ case METRIC_UNIT_MSEC:
+ return "miliSeconds";
+ case METRIC_UNIT_NUMBER:
+ return "Number";
+ case METRIC_UNIT_PERCENT:
+ return "Percent";
+ case METRIC_UNIT_DEVICE:
+ return "Device";
+ case METRIC_UNIT_STATUS:
+ return "Status";
+ case METRIC_UNIT_AUTOCHANGER:
+ return "Autochanger";
+ default:
+ return "Undefined";
+ }
+};
+
+/*
+ * Render and return a string representation of a metric value.
+ *
+ * in:
+ * buf - a pointer to POLLMEM buffer where value will be rendered
+ * bstr - defines a Boolean value rendering behavior; when True then rendered as True/False else as 1/0
+ * out:
+ * buf - rendered string
+ */
+void bstatmetric::render_metric_value(POOLMEM **buf, bool bstr)
+{
+ if (buf && *buf){
+ switch(type){
+ case METRIC_BOOL:
+ if (bstr){
+ Mmsg(buf, "%s", value.boolval ? "True":"False");
+ } else {
+ Mmsg(buf, "%s", value.boolval ? "1":"0");
+ }
+ break;
+ case METRIC_INT:
+ Mmsg(buf, "%lld", value.int64val);
+ break;
+ case METRIC_FLOAT:
+ Mmsg(buf, "%f", value.floatval);
+ break;
+ default:
+ pm_strcpy(buf, NULL);
+ break;
+ }
+ }
+};
+
+/*
+ * Render and return a string representation of a metric value.
+ *
+ * in:
+ * buf - a pointer to POLLMEM buffer where value will be rendered
+ * bstr - defines a Boolean value rendering behavior; when True then rendered as True/False else as 1/0
+ * out:
+ * buf - rendered string
+ */
+void bstatmetric::render_metric_value(POOL_MEM &buf, bool bstr)
+{
+ switch(type){
+ case METRIC_BOOL:
+ if (bstr){
+ Mmsg(buf, "%s", value.boolval ? "True":"False");
+ } else {
+ Mmsg(buf, "%s", value.boolval ? "1":"0");
+ }
+ break;
+ case METRIC_INT:
+ Mmsg(buf, "%lld", value.int64val);
+ break;
+ case METRIC_FLOAT:
+ Mmsg(buf, "%f", value.floatval);
+ break;
+ default:
+ pm_strcpy(buf, NULL);
+ break;
+ }
+};
+
+/*
+ * Default cache collector class.
+ */
+bstatcollect::bstatcollect()
+{
+ metrics = NULL;
+ nrmetrics = 0;
+ maxindex = 0;
+ size = 0;
+ if (pthread_mutex_init(&mutex, NULL) != 0){
+ /* leave uninitialized */
+ return;
+ }
+ metrics = (bstatmetric**)malloc(BSTATCOLLECT_NR * sizeof(bstatmetric*));
+ memset(metrics, 0, BSTATCOLLECT_NR * sizeof(bstatmetric*));
+ size = BSTATCOLLECT_NR;
+};
+
+/*
+ * Default destructor.
+ */
+bstatcollect::~bstatcollect()
+{
+ int a;
+
+ if (metrics){
+ for (a = 0; a < maxindex; a++){
+ if (metrics[a]){
+ /* the metric is allocated at this index, free it */
+ delete(metrics[a]);
+ }
+ }
+ free(metrics);
+ }
+ pthread_mutex_destroy(&mutex);
+};
+
+/*
+ * The cache collector lock synchronization for metrics table.
+ *
+ * This is a "global" lock for a particular class instance,
+ * so a single lock for the whole table. It could be a bottleneck
+ * on concurrent metric registration (first) and update (next)
+ * when a metric update will has to wait until registration
+ * complete with O(n).
+ * The solution/mitigation to this problem could be a number
+ * of independent locks that guards fixed parts of the metric
+ * table (i.e. a single lock for every 16 metrics). This allows
+ * to change the time when a lock is held before update to a fixed
+ * and limited latency. The registration process could take longer
+ * in this case as it could be required to acquire a number of locks
+ * instead of the one currently.
+ *
+ * As we do not expect more then a few hundreds metrics in the table
+ * above bottleneck should not be a problem.
+ */
+int bstatcollect::lock()
+{
+ int stat;
+
+ if (metrics){
+ if ((stat = pthread_mutex_lock(&mutex)) != 0){
+ return stat;
+ }
+ } else {
+ return EIO;
+ }
+ return 0;
+};
+
+/*
+ * The cache collector unlock synchronization for metrics table.
+ */
+int bstatcollect::unlock()
+{
+ int stat;
+
+ if (metrics){
+ if ((stat = pthread_mutex_unlock(&mutex)) != 0){
+ return stat;
+ }
+ } else {
+ return EIO;
+ }
+ return 0;
+};
+
+/*
+ * Verifies the size of metrics table and grows if required.
+ */
+void bstatcollect::check_size(int newsize)
+{
+ int a;
+ bstatmetric **newtable;
+
+ if (size < newsize + BSTATCOLLECT_STEP){
+ /* we have to extend our table */
+ newtable = (bstatmetric**)malloc((size + BSTATCOLLECT_STEP) * sizeof(bstatmetric*));
+ memset(newtable, 0, (size + BSTATCOLLECT_STEP) * sizeof(bstatmetric*));
+ for (a = 0; a < size; a++){
+ newtable[a] = metrics[a];
+ }
+ free(metrics);
+ metrics = newtable;
+ size += BSTATCOLLECT_STEP;
+ }
+};
+
+/*
+ * Finds the metric index for already existent metric or assigns the new index.
+ *
+ * in:
+ * metric - the metric name to find or index assignment
+ * out:
+ * metric index number
+ * ifalloc - False if index already allocated, True if new index assignments
+ */
+int bstatcollect::checkreg(char *metric, bool &ifalloc)
+{
+ int a;
+ int firstfree = METRIC_INVALID;
+ int index = METRIC_INVALID;
+
+ if (nrmetrics){
+ /* we have some metrics, so check it */
+ for (a = 0; a < maxindex; a++){
+ if (metrics[a]){
+ if (metrics[a]->name && bstrcmp(metrics[a]->name, metric)){
+ /* metric found return index */
+ ifalloc = false;
+ return a;
+ }
+ } else {
+ if (!(firstfree > METRIC_INVALID)){
+ firstfree = a;
+ }
+ }
+ }
+ /* not found, allocate new one */
+ index = firstfree > METRIC_INVALID ? firstfree : maxindex++;
+ check_size(index);
+ nrmetrics++;
+ } else {
+ /* no metrics, this will be the first one */
+ nrmetrics = 1;
+ maxindex = 1;
+ index = 0;
+ }
+ ifalloc = true;
+ return index;
+};
+
+/*
+ * Metric unregistration. Removes the metric from metrics table and frees resources.
+ * The metric index becomes invalid and can be reused on any subsequent metric registration.
+ * You should not use unregistered metrics index any more.
+ *
+ * in:
+ * metric - metric index to unregistration
+ * out:
+ * unregistered metric, when metric index was already invalid it no changes are made
+ */
+void bstatcollect::unregistration(int metric)
+{
+ if (metric > METRIC_INVALID && metric < maxindex && metrics[metric]){
+ delete(metrics[metric]);
+ metrics[metric] = NULL;
+ nrmetrics--;
+ }
+};
+
+/*
+ * Generic metric registration method. Allocates the new bstatmetric class if required.
+ *
+ * in:
+ * metric - metric name to register
+ * type - metric type
+ * unit - metric unit
+ * descr - description of the metric
+ * out:
+ * registered metric index - when metric already exist it return its index without allocating the new one
+ * you can get METRIC_INVALID value on any error
+ */
+int bstatcollect::registration(char *metric, metric_type_t type, metric_unit_t unit, char *descr)
+{
+ int index;
+ bstatmetric *data;
+ bool ifalloc;
+
+ if (lock() != 0){
+ return METRIC_INVALID;
+ }
+
+ index = checkreg(metric, ifalloc);
+ if (ifalloc){
+ data = New(bstatmetric(metric, type, unit, descr));
+ metrics[index] = data;
+ }
+
+ if (unlock() != 0){
+ return METRIC_INVALID;
+ }
+
+ return index;
+};
+
+/*
+ * Boolean metric registration and initialization. Allocates the new bstatmetric class if required.
+ * If metric already exist it will update the metric value.
+ *
+ * in:
+ * metric - metric name to register
+ * type - metric type
+ * unit - metric unit
+ * descr - description of the metric
+ * out:
+ * registered metric index - when metric already exist it return its index without allocating the new one
+ * you can get METRIC_INVALID value on any error
+ */
+int bstatcollect::registration_bool(char *metric, metric_unit_t unit, bool value, char *descr)
+{
+ int index;
+ bstatmetric *data;
+ bool ifalloc;
+
+ if (lock() != 0){
+ return METRIC_INVALID;
+ }
+
+ index = checkreg(metric, ifalloc);
+ if (ifalloc){
+ data = New(bstatmetric(metric, unit, value, descr));
+ metrics[index] = data;
+ } else {
+ metrics[index]->value.boolval = value;
+ }
+
+ if (unlock() != 0){
+ return METRIC_INVALID;
+ }
+
+ return index;
+};
+
+/*
+ * Integer metric registration and initialization. Allocates the new bstatmetric class if required.
+ * If metric already exist it will update the metric value.
+ *
+ * in:
+ * metric - metric name to register
+ * type - metric type
+ * unit - metric unit
+ * descr - description of the metric
+ * out:
+ * registered metric index - when metric already exist it return its index without allocating the new one
+ * you can get METRIC_INVALID value on any error
+ */
+int bstatcollect::registration_int64(char *metric, metric_unit_t unit, int64_t value, char *descr)
+{
+ int index;
+ bstatmetric *data;
+ bool ifalloc;
+
+ if (lock() != 0){
+ return METRIC_INVALID;
+ }
+
+ index = checkreg(metric, ifalloc);
+ if (ifalloc){
+ data = New(bstatmetric(metric, unit, value, descr));
+ metrics[index] = data;
+ } else {
+ metrics[index]->value.int64val = value;
+ }
+
+ if (unlock() != 0){
+ return METRIC_INVALID;
+ }
+
+ return index;
+};
+
+/*
+ * Floating point metric registration and initialization. Allocates the new bstatmetric class if required.
+ * If metric already exist it will update the metric value.
+ *
+ * in:
+ * metric - metric name to register
+ * type - metric type
+ * unit - metric unit
+ * descr - description of the metric
+ * out:
+ * registered metric index - when metric already exist it return its index without allocating the new one
+ * you can get METRIC_INVALID value on any error
+ */
+int bstatcollect::registration_float(char *metric, metric_unit_t unit, float value, char *descr)
+{
+ int index;
+ bstatmetric *data;
+ bool ifalloc;
+
+ if (lock() != 0){
+ return METRIC_INVALID;
+ }
+
+ index = checkreg(metric, ifalloc);
+ if (ifalloc){
+ data = New(bstatmetric(metric, unit, value, descr));
+ metrics[index] = data;
+ } else {
+ metrics[index]->value.floatval = value;
+ }
+
+ if (unlock() != 0){
+ return METRIC_INVALID;
+ }
+
+ return index;
+};
+
+/*
+ * Updates the Boolean metric value.
+ *
+ * in:
+ * metric - the metric index to update
+ * value - value to update (set)
+ * out:
+ * 0 when update was successful
+ * EINVAL when metric index is invalid or points to different metric type
+ * errno on any other error
+ */
+int bstatcollect::set_value_bool(int metric, bool value)
+{
+ int stat;
+ int rc = 0;
+
+ if (!metrics && !(metric > METRIC_INVALID) && !(metric < maxindex)){
+ return EINVAL;
+ }
+ if ((stat = lock()) != 0){
+ return stat;
+ }
+
+ if (metrics[metric] && metrics[metric]->type == METRIC_BOOL){
+ metrics[metric]->value.boolval = value;
+ } else {
+ rc = EINVAL;
+ }
+
+ if ((stat = unlock()) != 0){
+ return stat;
+ }
+ return rc;
+};
+
+/*
+ * Updates the Integer metric value.
+ *
+ * in:
+ * metric - the metric index to update
+ * value - value to update (set)
+ * out:
+ * 0 when update was successful
+ * EINVAL when metric index is invalid or points to different metric type
+ * errno on any other error
+ */
+int bstatcollect::set_value_int64(int metric, int64_t value)
+{
+ int stat;
+ int rc = 0;
+
+ if (!metrics && !(metric > METRIC_INVALID) && !(metric < maxindex)){
+ return EINVAL;
+ }
+ if ((stat = lock()) != 0){
+ return stat;
+ }
+
+ if (metrics[metric] && metrics[metric]->type == METRIC_INT){
+ metrics[metric]->value.int64val = value;
+ } else {
+ rc = EINVAL;
+ }
+
+ if ((stat = unlock()) != 0){
+ return stat;
+ }
+ return rc;
+};
+
+/*
+ * Updates the Integer metric value by adding the number.
+ *
+ * in:
+ * metric - the metric index to update
+ * value - value to update (add)
+ * out:
+ * 0 when update was successful
+ * EINVAL when metric index is invalid or points to different metric type
+ * errno on any other error
+ */
+int bstatcollect::add_value_int64(int metric, int64_t value)
+{
+ int stat;
+ int rc = 0;
+
+ if (!metrics && !(metric > METRIC_INVALID) && !(metric < maxindex)){
+ return EINVAL;
+ }
+ if ((stat = lock()) != 0){
+ return stat;
+ }
+
+ if (metrics[metric] && metrics[metric]->type == METRIC_INT){
+ metrics[metric]->value.int64val += value;
+ } else {
+ rc = EINVAL;
+ }
+
+ if ((stat = unlock()) != 0){
+ return stat;
+ }
+ return rc;
+};
+
+/*
+ * Updates two Integer metric values by adding corresponding numbers.
+ *
+ * in:
+ * metric1 - the metric index to update
+ * value1 - value to update (add)
+ * metric2 - the metric index to update
+ * value2 - value to update (add)
+ * out:
+ * 0 when update was successful
+ * EINVAL when metric index is invalid or points to different metric type
+ * errno on any other error
+ */
+int bstatcollect::add2_value_int64(int metric1, int64_t value1, int metric2, int64_t value2)
+{
+ int stat;
+ int rc = 0;
+
+ if (!metrics && ((!(metric1 > METRIC_INVALID) && !(metric1 < maxindex))
+ || (!(metric2 > METRIC_INVALID) && !(metric2 < maxindex)))){
+ return EINVAL;
+ }
+ if ((stat = lock()) != 0){
+ return stat;
+ }
+
+ if (metrics[metric1] && metrics[metric1]->type == METRIC_INT){
+ metrics[metric1]->value.int64val += value1;
+ } else {
+ rc = EINVAL;
+ }
+ if (metrics[metric2] && metrics[metric2]->type == METRIC_INT){
+ metrics[metric2]->value.int64val += value2;
+ } else {
+ rc = EINVAL;
+ }
+
+ if ((stat = unlock()) != 0){
+ return stat;
+ }
+ return rc;
+};
+
+/*
+ * Updates the Integer metric value by substitute the number.
+ *
+ * in:
+ * metric - the metric index to update
+ * value - value to update (substitute)
+ * out:
+ * 0 when update was successful
+ * EINVAL when metric index is invalid or points to different metric type
+ * errno on any other error
+ */
+int bstatcollect::sub_value_int64(int metric, int64_t value)
+{
+ return add_value_int64(metric, -value);
+};
+
+/*
+ * Updates the Floating point metric value.
+ *
+ * in:
+ * metric - the metric index to update
+ * value - value to update (set)
+ * out:
+ * 0 when update was successful
+ * EINVAL when metric index is invalid or points to different metric type
+ * errno on any other error
+ */
+int bstatcollect::set_value_float(int metric, float value)
+{
+ int stat;
+ int rc = 0;
+
+ if (!metrics && !(metric > METRIC_INVALID) && !(metric < maxindex)){
+ return EINVAL;
+ }
+ if ((stat = lock()) != 0){
+ return stat;
+ }
+
+ if (metrics[metric] && metrics[metric]->type == METRIC_FLOAT){
+ metrics[metric]->value.floatval = value;
+ } else {
+ rc = EINVAL;
+ }
+
+ if ((stat = unlock()) != 0){
+ return stat;
+ }
+ return rc;
+};
+
+/*
+ * Updates the Integer metric value by incrementing it.
+ *
+ * in:
+ * metric - the metric index to update
+ * value - value to update (set)
+ * out:
+ * 0 when update was successful
+ * EINVAL when metric index is invalid or points to different metric type
+ * errno on any other error
+ */
+int bstatcollect::inc_value_int64(int metric)
+{
+ int stat;
+ int rc = 0;
+
+ if (!metrics && !(metric > METRIC_INVALID) && !(metric < maxindex)){
+ return EINVAL;
+ }
+ if ((stat = lock()) != 0){
+ return stat;
+ }
+
+ if (metrics[metric] && metrics[metric]->type == METRIC_INT){
+ metrics[metric]->value.int64val++;
+ } else {
+ rc = EINVAL;
+ }
+
+ if ((stat = unlock()) != 0){
+ return stat;
+ }
+ return rc;
+};
+
+/*
+ * Updates the Integer metric value by incrementing it.
+ *
+ * in:
+ * metric - the metric index to update
+ * value - value to update (set)
+ * out:
+ * 0 when update was successful
+ * EINVAL when metric index is invalid or points to different metric type
+ * errno on any other error
+ */
+int bstatcollect::dec_value_int64(int metric)
+{
+ int stat;
+ int rc = 0;
+
+ if (!metrics && !(metric > METRIC_INVALID) && !(metric < maxindex)){
+ return EINVAL;
+ }
+ if ((stat = lock()) != 0){
+ return stat;
+ }
+
+ if (metrics[metric] && metrics[metric]->type == METRIC_INT){
+ metrics[metric]->value.int64val--;
+ } else {
+ rc = EINVAL;
+ }
+
+ if ((stat = unlock()) != 0){
+ return stat;
+ }
+ return rc;
+};
+
+/*
+ *
+ */
+int bstatcollect::dec_inc_values_int64(int metricd, int metrici)
+{
+ int rc = 0;
+
+ lock();
+ if (!metrics && !(metricd > METRIC_INVALID) && !(metricd < maxindex)
+ && !(metrici > METRIC_INVALID) && !(metrici < maxindex)){
+ rc = EINVAL;
+ } else
+ if (metrics[metricd] && metrics[metricd]->type == METRIC_INT &&
+ metrics[metrici] && metrics[metrici]->type == METRIC_INT){
+ metrics[metricd]->value.int64val--;
+ metrics[metrici]->value.int64val++;
+ } else {
+ rc = EINVAL;
+ }
+ unlock();
+
+ return rc;
+};
+
+/*
+ * Return the Boolean value from metric.
+ * If metric index is invalid it will always return false.
+ * It does not check if the metric type is Boolean.
+ */
+bool bstatcollect::get_bool(int metric)
+{
+ bool val = false;
+
+ lock();
+ if (metrics && metric > METRIC_INVALID && metric < maxindex && metrics[metric]){
+ val = metrics[metric]->value.boolval;
+ }
+ unlock();
+ return val;
+};
+
+/*
+ * Return the Integer value from metric.
+ * If metric index is invalid it will always return zero.
+ * It does not check if the metric type is Integer.
+ */
+int64_t bstatcollect::get_int(int metric)
+{
+ int64_t val = 0;
+
+ lock();
+ if (metrics && metric > METRIC_INVALID && metric < maxindex && metrics[metric]){
+ val = metrics[metric]->value.int64val;
+ }
+ unlock();
+ return val;
+};
+
+/*
+ * Return the Floating point value from metric.
+ * If metric index is invalid it will always return zero.
+ * It does not check if the metric type is Floating point.
+ */
+float bstatcollect::get_float(int metric)
+{
+ float val = 0;
+
+ lock();
+ if (metrics && metric > METRIC_INVALID && metric < maxindex && metrics[metric]){
+ val = metrics[metric]->value.floatval;
+ }
+ unlock();
+ return val;
+};
+
+/*
+ * Return all available (registered) metrics from metrics table as an array list.
+ *
+ * in:
+ * out:
+ * alist - a newly allocated array list with all current metrics (bstatmetric); every metrics
+ * in the array list returned are newly allocated; all returned resources (array list and bstat metrics)
+ * should be freed when no needed. you can use free_metric_alist() for that.
+ */
+alist *bstatcollect::get_all()
+{
+ int a;
+ alist *list = NULL;
+ bstatmetric *data;
+
+ if (metrics){
+ list = New(alist(BSTATCOLLECT_NR, not_owned_by_alist));
+ lock();
+ for (a = 0; a < maxindex; a++){
+ if (metrics[a]){
+ data = New(bstatmetric);
+#ifdef TEST_PROGRAM_DEBUG
+ Pmsg0(0, "copy bstatmetric ...\n");
+#endif
+ *data = *metrics[a];
+ list->append(data);
+ }
+ }
+ unlock();
+ }
+ return list;
+};
+
+/*
+ * Return the bstatmetric class pointed by a metric name.
+ * If metric name is invalid or not found or collector class has no metrics allocated
+ * it will always return Null.
+ *
+ * in:
+ * metric - a metric name to search
+ * out:
+ * bstatmetric - a newly allocated and copied bstatmetric class when metric was found
+ * Null when metric not found
+ */
+bstatmetric *bstatcollect::get_metric(char* metric)
+{
+ int a;
+ bstatmetric *data = NULL;
+
+ if (nrmetrics && metrics && metric){
+ /* we have some metrics, so check it */
+ lock();
+ for (a = 0; a < maxindex; a++){
+ if (metrics[a]){
+ if (metrics[a]->name && bstrcmp(metrics[a]->name, metric)){
+ /* metric found return a copy of it */
+ data = New(bstatmetric);
+ *data = *metrics[a];
+ break;
+ }
+ }
+ }
+ unlock();
+ }
+ return data;
+};
+
+/*
+ * Return the bstatmetric class pointed by a metric name.
+ * If metric name is invalid or not found or collector class has no metrics allocated
+ * it will always return Null.
+ *
+ * in:
+ * metric - a metric name to search
+ * out:
+ * bstatmetric - a newly allocated and copied bstatmetric class when metric was found
+ * Null when metric not found
+ */
+bstatmetric *bstatcollect::get_metric(int metric)
+{
+ bstatmetric *data = NULL;
+
+ lock();
+ if (nrmetrics && metrics && metric > METRIC_INVALID && metric < maxindex && metrics[metric]){
+ data = New(bstatmetric);
+ *data = *metrics[metric];
+ }
+ unlock();
+ return data;
+};
+
+/*
+ * Supported function releases array list of metric returned by get_all().
+ */
+void free_metric_alist(alist *list)
+{
+ bstatmetric *item;
+
+ if (list){
+ foreach_alist(item, list){
+ delete (item);
+ }
+ delete(list);
+ }
+};
+
+#ifndef TEST_PROGRAM
+#define TEST_PROGRAM_A
+#endif
+
+/*
+ * Dumps the content of bstatmetric class.
+ * Used for debugging only outside Bacula core.
+ */
+void bstatmetric::dump()
+{
+#ifdef TEST_PROGRAM
+ char ed1[50];
+
+ Pmsg4(-1, "name=\"%s\" type=%s unit=%s descr=\"%s\" value=",
+ name ? name : "<*NULL*>",
+ metric_type_str(),
+ metric_unit_str(),
+ description ? description : "<*NULL*>");
+ switch (type){
+ case METRIC_BOOL:
+ Pmsg1(-1, "%s\n", value.boolval ? "True" : "False");
+ break;
+ case METRIC_INT:
+ Pmsg1(-1, "%s\n", edit_uint64(value.int64val, ed1));
+ break;
+ case METRIC_FLOAT:
+ Pmsg1(-1, "%f\n", value.floatval);
+ break;
+ default:
+ Pmsg0(-1, "<unknown>\n");
+ break;
+ }
+#endif
+};
+
+/*
+ * Dumps the content of collector class including all metrics dump.
+ * Used for debugging only outside Bacula core.
+ */
+void bstatcollect::dump()
+{
+#ifdef TEST_PROGRAM
+ int a;
+
+ Pmsg1(-1, "\tbstatcollect::size %d\n", size);
+ Pmsg1(-1, "\tbstatcollect::nrmetrics %d\n", nrmetrics);
+ Pmsg1(-1, "\tbstatcollect::maxindex %d\n", maxindex);
+ if (metrics){
+ for (a = 0; a < maxindex; a++){
+ if (metrics[a]){
+ Pmsg1(-1, "\tbstatcollect::metric[%d]: ", a);
+ metrics[a]->dump();
+ } else {
+ Pmsg1(-1, "\tbstatcollect::metric[%d]: EMPTY SLOT\n", a);
+ }
+ }
+ } else {
+ Pmsg0(-1, "\tbstatcollect uninitialized\n");
+ }
+#endif
+};
+
+#ifdef TEST_PROGRAM
+#include "unittests.h"
+
+int main()
+{
+ Unittests bstat_test("bstat_test", true);
+ bstatcollect *collector;
+ int m1, m1a, m2, m2a, m3, m4;
+ alist *all;
+ int rc;
+ bstatmetric *item;
+ char *metric1 = (char*)"bacula.test.metric";
+ char *metric2 = (char*)"bacula.test.other";
+ char *metric3 = (char*)"bacula.test.third";
+ char *metric4 = (char*)"bacula.test.four.bool";
+ char *descr1 = (char*)"Test bacula.test.metric description";
+ char *descr2 = (char*)"Test bacula.test.other description";
+ char *descr3 = (char*)"Test bacula.test.third description";
+ char *descr4 = (char*)"Test bacula.test.four.bool description";
+
+ Pmsg0(0, "Initialize tests ...\n");
+
+ collector = New(bstatcollect);
+ ok(collector->_check_size(BSTATCOLLECT_NR) &&
+ collector->_check_nrmetrics(0) &&
+ collector->_get_maxindex(0),
+ "Default initialization");
+
+ Pmsg0(0, "Simple registration tests ...\n");
+
+ m1 = collector->registration_int64(metric1, METRIC_UNIT_JOB, (int64_t)123, descr1);
+ ok(collector->_check_nrmetrics(1), "Registration int64_t nrmetrics");
+ ok(collector->_get_maxindex(1), "Registration int64_t maxindex");
+ ok(m1 == 0, "Registration int64_t metricid");
+
+ m2 = collector->registration_float(metric2, METRIC_UNIT_PERCENT, (float)90.0, descr2);
+ ok(collector->_check_nrmetrics(2), "Registration float nrmetrics");
+ ok(collector->_get_maxindex(2), "Registration float maxindex");
+ ok(m2 == 1, "Registration float metricid");
+
+ m4 = collector->registration(metric4, METRIC_BOOL, METRIC_UNIT_EMPTY, descr4);
+ ok(collector->_check_nrmetrics(3), "Registration METRIC_BOOL nrmetrics");
+ ok(collector->_get_maxindex(3), "Registration METRIC_BOOL maxindex");
+ ok(m4 == 2, "Registration METRIC_BOOL metricid");
+
+ Pmsg0(0, "Update tests ...\n");
+
+ collector->set_value_int64(m1, 234);
+ ok(collector->get_int(m1), "Set int64_t");
+
+ collector->set_value_float(m2, 10.0);
+ ok(collector->get_float(m2), "Set float");
+
+ collector->set_value_bool(m4, true);
+ ok(collector->get_bool(m4), "Set bool");
+
+ rc = collector->set_value_float(m1, -1.234);
+ ok(rc, "Update int64_t by float return status");
+ ok(collector->get_int(m1) == 234, "Update int64_t by float value");
+
+ rc = collector->add_value_int64(m2, 1234);
+ ok(rc, "Add int64_t on float return status");
+ ok(collector->get_float(m2) == 10.0, "Add int64_t on float value");
+
+ Pmsg0(0, "Double Registration tests ...\n");
+
+ m1a = collector->registration_int64(metric1, METRIC_UNIT_JOB, (int64_t)321, descr2);
+ ok(m1 == m1a, "Registration metricid");
+ item = collector->get_metric(m1);
+ ok(item->value.int64val == 321, "New metric value");
+ ok(strcmp(descr2, item->description), "New metric descr");
+ delete(item);
+
+ m2a = collector->registration(metric2, METRIC_INT, METRIC_UNIT_JOB, descr3);
+ ok(m2 == m2a, "Registration metricid");
+
+ Pmsg0(0, "Unregistration tests ...\n");
+ collector->unregistration(m1);
+ ok(collector->_check_nrmetrics(2), "Unregistration nrmetrics");
+ ok(collector->_get_maxindex(3), "Unregistration maxindex");
+ item = collector->get_metric(m1);
+ nok(item != NULL, "Get unregistered metric");
+ if (item){
+ item->dump();
+ delete(item);
+ }
+ m3 = collector->registration_int64(metric3, METRIC_UNIT_BYTE, (int64_t)999, descr3);
+ ok(collector->_check_nrmetrics(3), "Registration again nrmetrics");
+ ok(collector->_get_maxindex(3), "Registration again maxindex");
+ ok(m3 == 0, "Registration again metricid");
+
+ Pmsg0(0, "Get value tests ...\n");
+ ok(collector->get_float(m2) == 10.0, "Get value float");
+ ok(collector->get_int(m3) == 999, "Get value int64_t");
+ ok(collector->get_bool(m4), "Get value bool");
+ nok(collector->get_int(m3+1000), "Get value from nonexistent metric");
+
+ Pmsg0(0, "Get all values list tests ...\n");
+ all = collector->get_all();
+ ok(all != NULL, "Return list pointer");
+ ok(all->size() == 3, "Return list size");
+
+ Pmsg0(0, "All metrics list:\n");
+ foreach_alist(item, all){
+ item->dump();
+ }
+ free_metric_alist(all);
+ delete(collector);
+ return report();
+};
+
+#endif /* TEST_PROGRAM */
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2016 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Radosław Korzeniewski, MMXVIII
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * This is a Bacula statistics internal collector.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#ifndef __BSTAT_H_
+#define __BSTAT_H_
+#include "bacula.h"
+
+
+#define METRIC_INVALID -1
+#define BSTATCOLLECT_NR 100
+#define BSTATCOLLECT_STEP 10
+
+/* metric types supported */
+typedef enum {
+ METRIC_UNDEF = 0,
+ METRIC_INT,
+ METRIC_BOOL,
+ METRIC_FLOAT,
+} metric_type_t;
+
+/* metric units supported */
+typedef enum {
+ METRIC_UNIT_EMPTY = 0,
+ METRIC_UNIT_BYTE,
+ METRIC_UNIT_BYTESEC,
+ METRIC_UNIT_JOB,
+ METRIC_UNIT_CLIENT,
+ METRIC_UNIT_FILESET,
+ METRIC_UNIT_POOL,
+ METRIC_UNIT_STORAGE,
+ METRIC_UNIT_SCHEDULE,
+ METRIC_UNIT_FILE,
+ METRIC_UNIT_VOLUME,
+ METRIC_UNIT_SEC,
+ METRIC_UNIT_MSEC,
+ METRIC_UNIT_NUMBER,
+ METRIC_UNIT_STATUS,
+ METRIC_UNIT_PERCENT,
+ METRIC_UNIT_DEVICE,
+ METRIC_UNIT_AUTOCHANGER,
+} metric_unit_t;
+// some aliases
+#define METRIC_UNIT_BYTES METRIC_UNIT_BYTE
+
+/* the metric value */
+typedef union {
+ int64_t int64val;
+ float floatval;
+ bool boolval;
+} metric_value_t;
+
+/* This is a metric class */
+class bstatmetric : public SMARTALLOC {
+public:
+ char *name; /* this is a metric name */
+ metric_type_t type; /* this is a metric type */
+ metric_unit_t unit; /* this is a metric unit */
+ metric_value_t value; /* this is a metric value */
+ char *description; /* this is a metric description */
+ bstatmetric();
+ bstatmetric(char *mname, metric_type_t mtype, metric_unit_t munit, char *descr);
+ bstatmetric(char *mname, metric_unit_t munit, bool mvalue, char *descr);
+ bstatmetric(char *mname, metric_unit_t munit, int64_t mvalue, char *descr);
+ bstatmetric(char *mname, metric_unit_t munit, float mvalue, char *descr);
+ ~bstatmetric();
+ bstatmetric& operator=(const bstatmetric& orig);
+ void render_metric_value(POOLMEM **buf, bool bstr=false);
+ void render_metric_value(POOL_MEM &buf, bool bstr=false);
+ const char *metric_type_str();
+ const char *metric_unit_str();
+ void dump();
+private:
+ void init(char *mname, metric_type_t mtype, metric_unit_t munit, char *descr);
+};
+
+/* update statcollector with macros */
+#define collector_update_set_value_bool(sc, m, v) (sc && sc->set_value_bool(m, v))
+#define collector_update_set_value_int64(sc, m, v) (sc && sc->set_value_int64(m, v))
+#define collector_update_add_value_int64(sc, m, v) (sc && sc->add_value_int64(m, v))
+#define collector_update_add2_value_int64(sc, m1, v1, m2, v2) (sc && sc->add2_value_int64(m1, v1, m2, v2))
+#define collector_update_sub_value_int64(sc, m, v) (sc && sc->sub_value_int64(m, v))
+#define collector_update_set_value_float(sc, m, v) (sc && sc->set_value_float(m, v))
+#define collector_update_inc_value_int64(sc, m) (sc && sc->inc_value_int64(m))
+#define collector_update_dec_value_int64(sc, m) (sc && sc->dec_value_int64(m))
+#define collector_update_dec_inc_values_int64(sc, md, mi) (sc && sc->dec_inc_values_int64(md, mi))
+
+/* This is an internal collector class */
+class bstatcollect : public SMARTALLOC {
+ bstatmetric **metrics;
+ int size;
+ int nrmetrics;
+ int maxindex;
+ pthread_mutex_t mutex;
+
+ int lock();
+ int unlock();
+ void check_size(int newsize);
+ int checkreg(char *metric, bool &ifalloc);
+
+public:
+ bstatcollect();
+ ~bstatcollect();
+
+ /* registration return a metric index */
+ int registration(char *metric, metric_type_t type, metric_unit_t unit, char *descr);
+ int registration(const char *metric, metric_type_t type, metric_unit_t unit, const char *descr){
+ return registration((char*)metric, type, unit, (char*)descr);
+ };
+ int registration(char *metric, metric_type_t type, metric_unit_t unit, const char *descr){
+ return registration(metric, type, unit, (char*)descr);
+ };
+ int registration_bool(char *metric, metric_unit_t unit, bool value, char *descr);
+ int registration_int64(char *metric, metric_unit_t unit, int64_t value, char *descr);
+ int registration_int64(const char *metric, metric_unit_t unit, int64_t value, const char *descr){
+ return registration_int64((char*)metric, unit, value, (char*)descr);
+ };
+ int registration_int64(char *metric, metric_unit_t unit, int64_t value, const char *descr){
+ return registration_int64(metric, unit, value, (char*)descr);
+ };
+ int registration_float(char *metric, metric_unit_t unit, float value, char *descr);
+ /* unregistration */
+ void unregistration(int metric);
+ /* update/set the metric value */
+ int set_value_bool(int metric, bool value);
+ int set_value_int64(int metric, int64_t value);
+ int add_value_int64(int metric, int64_t value);
+ int add2_value_int64(int metric1, int64_t value1, int metric2, int64_t value2);
+ int sub_value_int64(int metric, int64_t value);
+ int set_value_float(int metric, float value);
+ int inc_value_int64(int metric);
+ int dec_value_int64(int metric);
+ int dec_inc_values_int64(int metricd, int metrici);
+ /* get data */
+ bool get_bool(int metric);
+ int64_t get_int(int metric);
+ float get_float(int metric);
+ alist *get_all();
+ bstatmetric *get_metric(int metric);
+ bstatmetric *get_metric(char *metric);
+ /* utility */
+ void dump();
+ /* for unit tests only */
+ int _check_size(int n) { return size == n; };
+ int _check_nrmetrics(int n) { return nrmetrics == n; };
+ int _get_maxindex(int n) { return maxindex == n; };
+};
+
+/* supported utilities */
+void free_metric_alist(alist *list);
+
+#endif /* __BSTAT_H_ */
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2017 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Radosław Korzeniewski, MMXVIII
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * This is a Bacula statistics support utilities.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#include "bacula.h"
+
+/*
+ * Scans a display format parameter and return appropriate enum value.
+ *
+ * in:
+ * buf - the command argument string to check
+ * out:
+ * appropriate display_format_t value, defaults to COLLECT_SIMPLE
+ */
+display_format_t scandisplayformat(POOLMEM *buf)
+{
+ if (bstrcmp(buf, "json")){
+ return COLLECT_JSON;
+ }
+ if (bstrcmp(buf, "full")){
+ return COLLECT_FULL;
+ }
+ return COLLECT_SIMPLE;
+};
+
+/*
+ * Render a simple representation of the metric into a buffer.
+ *
+ * in:
+ * out - a memory buffer where render to
+ * m - a bstatmetric object to render its value
+ * out:
+ * rendered metric name and value in simple format at buf
+ */
+void rendermetricsimple(POOL_MEM &out, bstatmetric *m)
+{
+ POOL_MEM buf(PM_MESSAGE);
+
+ m->render_metric_value(buf);
+ Mmsg(out, "%s=%s\n", m->name, buf.c_str());
+};
+
+/*
+ * Render a JSON representation of the metric into a buffer.
+ *
+ * in:
+ * out - a memory buffer where render to
+ * m - a bstatmetric object to render its value
+ * out:
+ * rendered metric in JSON format at buf
+ *
+ * JSON example output implemented:
+ * [
+ * {
+ * name: "bacula.jobs.all",
+ * value: 228,
+ * type: "Integer",
+ * unit: "Jobs",
+ * description: "Number of all jobs."
+ * },
+ * ]
+ * - the array brackets are delivered outside this function
+ */
+void rendermetricjson(POOL_MEM &out, bstatmetric *m, int nr)
+{
+ POOL_MEM buf(PM_MESSAGE);
+
+ m->render_metric_value(buf, true);
+ Mmsg(out, "%s {\n \"name\": \"%s\",\n \"value\": %s,\n \"type\": \"%s\",\n \"unit\": \"%s\",\n \"description\": \"%s\"\n }",
+ nr > 0 ? ",\n":"\n", m->name, buf.c_str(), m->metric_type_str(), m->metric_unit_str(), m->description);
+};
+
+/*
+ * Render a full representation of the metric into a buffer.
+ *
+ * in:
+ * out - a memory buffer where render to
+ * m - a bstatmetric object to render its value
+ * out:
+ * rendered metric at buf
+ */
+void rendermetricfull(POOL_MEM &out, bstatmetric *m)
+{
+ POOL_MEM buf(PM_MESSAGE);
+
+ m->render_metric_value(buf);
+ Mmsg(out, "name=\"%s\" value=%s type=%s unit=%s descr=\"%s\"\n", m->name, buf.c_str(), m->metric_type_str(),
+ m->metric_unit_str(), m->description);
+};
+
+/*
+ * Render metric into a buffer based on display format provided.
+ *
+ * in:
+ * out - a memory buffer where render to
+ * m - a bstatmetric object to render its value
+ * format - display format enum
+ * out:
+ * rendered metric at buf
+ */
+void rendermetric(POOL_MEM &out, bstatmetric *m, display_format_t format, int nr)
+{
+ switch (format){
+ case COLLECT_SIMPLE:
+ rendermetricsimple(out, m);
+ break;
+ case COLLECT_JSON:
+ rendermetricjson(out, m, nr);
+ break;
+ case COLLECT_FULL:
+ rendermetricfull(out, m);
+ break;
+ }
+};
+
+/*
+ * Return a string representation of the display format enum.
+ */
+const char *displayformat2str(display_format_t format)
+{
+ switch (format){
+ case COLLECT_SIMPLE:
+ return "simple";
+ case COLLECT_JSON:
+ return "json";
+ case COLLECT_FULL:
+ return "full";
+ default:
+ return "simple";
+ }
+};
+
+/*
+ * Return a string representation of the collector status.
+ *
+ * in:
+ * res_collector - a COLLECTOR resource for collector backend
+ * out:
+ * string representation of the collector status
+ */
+const char *str_collector_status(COLLECTOR &res_collector)
+{
+ const char *status;
+
+ if (res_collector.valid){
+ status = res_collector.running?"running":"stopped";
+ } else {
+ status = res_collector.running?"waiting to exit":"stopped";
+ }
+ return status;
+};
+
+/*
+ * Return a string representation of the collector spooling status.
+ *
+ * in:
+ * res_collector - a COLLECTOR resource for collector backend
+ * out:
+ * string representation of the collector spooling status
+ */
+const char *str_collector_spooling(COLLECTOR &res_collector)
+{
+ const char *spool;
+
+ if (res_collector.spool_directory){
+ /* spooling defined */
+ switch (res_collector.spooled){
+ case BCOLLECT_SPOOL_YES:
+ spool = "in progress";
+ break;
+ case BCOLLECT_SPOOL_DESPOOL:
+ spool = "despooling now";
+ break;
+ case BCOLLECT_SPOOL_NO:
+ spool = "enabled";
+ break;
+ default:
+ spool = "unknown (enabled)";
+ }
+ } else {
+ spool = "disabled";
+ }
+ return spool;
+};
+
+/*
+ * A support function renders a Statistics resource status into a buffer.
+ *
+ * in:
+ * res_collector - a COLLECTOR resource class to display
+ * buf - a POLL_MEM buffer to render into
+ * out:
+ * the length of rendered text
+ */
+int render_collector_status(COLLECTOR &res_collector, POOL_MEM &buf)
+{
+ const char *status, *spool;
+ char dt[MAX_TIME_LENGTH];
+ time_t t;
+ utime_t i;
+ int len;
+ POOL_MEM errmsg(PM_MESSAGE);
+
+ res_collector.lock();
+ status = str_collector_status(res_collector);
+ t = res_collector.timestamp;
+ i = res_collector.interval;
+ spool = str_collector_spooling(res_collector);
+ if (res_collector.errmsg && strlen(res_collector.errmsg)){
+ Mmsg(errmsg, " lasterror=%s\n", res_collector.errmsg);
+ } else {
+ pm_strcpy(errmsg, "");
+ }
+ res_collector.unlock();
+
+ bstrftime_nc(dt, sizeof(dt), t);
+ len = Mmsg(buf, "Statistics backend: %s is %s\n type=%i lasttimestamp=%s\n interval=%d secs\n spooling=%s\n%s\n",
+ res_collector.hdr.name, status,
+ res_collector.type, dt,
+ i, spool,
+ errmsg.c_str());
+ return len;
+};
+
+/*
+ * A support function renders a Statistics resource status into an OutputWriter for APIv2.
+ *
+ * in:
+ * res_collector - a COLLECTOR resource class to display
+ * ow - OutputWriter for apiv2
+ * out:
+ * rendered status in OutputWritter buffer
+ */
+void api_render_collector_status(COLLECTOR &res_collector, OutputWriter &ow)
+{
+ const char *status, *spool;
+ time_t t;
+ utime_t i;
+
+ res_collector.lock();
+ status = str_collector_status(res_collector);
+ t = res_collector.timestamp;
+ i = res_collector.interval;
+ spool = str_collector_spooling(res_collector);
+ res_collector.unlock();
+ ow.get_output(
+ OT_START_OBJ,
+ OT_STRING, "name", res_collector.hdr.name,
+ OT_STRING, "status", status,
+ OT_INT, "interval", i,
+ OT_UTIME, "lasttimestamp", t,
+ OT_STRING, "spooling", spool,
+ OT_STRING, "lasterror", NPRTB(res_collector.errmsg),
+ OT_END_OBJ,
+ OT_END
+ );
+ return;
+};
+
+/*
+ * Replace dot '.' character for "%32" to avoid metric level split
+ */
+char *replace_dot_metric_name(POOL_MEM &out, const char *name)
+{
+ char *p, *q;
+ POOL_MEM tmp(PM_NAME);
+
+ pm_strcpy(out, NULL);
+ pm_strcpy(tmp, name);
+ p = tmp.c_str();
+ while((q = strchr(p, '.')) != NULL){
+ /* q is the dot char and p is a start of the substring to copy */
+ *q = 0; // temporary terminate substring
+ pm_strcat(out, p);
+ pm_strcat(out, "%32");
+ // printf(">%s<", out.c_str());
+ p = q + 1; // next substring
+ }
+ pm_strcat(out, p);
+ return out.c_str();
+};
+
+/*
+ * Return the metric name updated with Statistics Prefix parameter if defined.
+ */
+void render_metric_prefix(COLLECTOR *collector, POOL_MEM &buf, bstatmetric *item)
+{
+ POOL_MEM name(PM_NAME);
+
+ if (collector && item){
+ if (collector->mangle_name){
+ replace_dot_metric_name(name, item->name);
+ } else {
+ Mmsg(name, "%s", item->name);
+ }
+ if (collector->prefix){
+ /* $prefix.$metric */
+ Mmsg(buf, "%s.%s", collector->prefix, name.c_str());
+ } else {
+ /* $metric */
+ Mmsg(buf, "%s", name.c_str());
+ }
+ Dmsg2(1500, "Statistics: %s met&prefix: %s\n", collector->hdr.name, buf.c_str());
+ }
+};
+
+
+#ifndef TEST_PROGRAM
+#define TEST_PROGRAM_A
+#endif
+
+#ifdef TEST_PROGRAM
+#include "unittests.h"
+
+struct test {
+ const int nr;
+ const char *in;
+ const char *out;
+};
+
+static struct test tests[] = {
+ {1, "abc.def", "abc%32def"},
+ {2, "", ""},
+ {3, ".abc", "%32abc"},
+ {4, "abc.", "abc%32"},
+ {5, "abc..def", "abc%32%32def"},
+ {6, "abc..", "abc%32%32"},
+ {7, "..def", "%32%32def"},
+ {8, ".......", "%32%32%32%32%32%32%32"},
+ {0, NULL, NULL},
+};
+
+#define ntests ((int)(sizeof(tests)/sizeof(struct test)))
+
+int main()
+{
+ Unittests collect_test("collect_test");
+ POOL_MEM name(PM_NAME);
+ char buf[64];
+
+ for (int i = 0; i < ntests; i++) {
+ if (tests[i].nr > 0){
+ snprintf(buf, 64, "Checking mangle test: %d - '%s'", tests[i].nr, tests[i].in);
+ ok(strcmp(replace_dot_metric_name(name, tests[i].in), tests[i].out) == 0, buf);
+ }
+ }
+
+ return report();
+}
+#endif /* TEST_PROGRAM */
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2017 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Radosław Korzeniewski, MMXVIII
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * This is a Bacula statistics collector support utilities.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ */
+
+#ifndef __COLLECT_H_
+#define __COLLECT_H_
+
+# include "bstat.h"
+
+/* all supported display formats */
+typedef enum {
+ COLLECT_SIMPLE = 0,
+ COLLECT_FULL,
+ COLLECT_JSON,
+} display_format_t;
+
+/* scanning commands for remote metrics query */
+const char collect_all_cmd[] = "statistics all format=%127s";
+const char collect_metrics_cmd[] = "statistics format=%127s %127s";
+const char collect_all_send_cmd[] = "statistics all format=%s\n";
+const char collect_metrics_send_cmd[] = "statistics format=%s";
+
+/* forward reference only */
+class bstatmetric;
+/* forward reference only */
+class COLLECTOR;
+class OutputWriter;
+
+const char *displayformat2str(display_format_t format);
+display_format_t scandisplayformat(POOLMEM *buf);
+//void rendervalue(POOL_MEM &buf, bstatmetric *m);
+//void rendermetricsimple(POOL_MEM &out, bstatmetric *m);
+//void rendermetricjson(POOL_MEM &out, bstatmetric *m);
+//void rendermetricfull(POOL_MEM &out, bstatmetric *m);
+void rendermetric(POOL_MEM &out, bstatmetric *m, display_format_t format, int nr);
+void render_metric_prefix(COLLECTOR *collector, POOL_MEM &buf, bstatmetric *item);
+int render_collector_status(COLLECTOR &res_collector, POOL_MEM &buf);
+void api_render_collector_status(COLLECTOR &res_collector, OutputWriter &ow);
+
+#endif /* __COLLECT_H */
#include "queue.h"
#include "serial.h"
#include "message.h"
+#include "bcollector.h"
#include "openssl.h"
#include "lex.h"
#include "parse_conf.h"
#include "output.h"
#include "protos.h"
#include "bget_msg.h"
+#include "bstat.h"
+#include "collect.h"
+
Public License, v3.0 ("AGPLv3") and some additional permissions and
terms pursuant to its AGPLv3 Section 7.
- This notice must be preserved when any source code is
+ This notice must be preserved when any source code is
conveyed and/or propagated.
Bacula(R) is a registered trademark of Kern Sibbald.
union URES {
MSGS res_msgs;
RES hdr;
+ COLLECTOR res_collector;
};
#if defined(_MSC_VER)
{NULL, NULL, {0}, 0, 0, 0}
};
+/*
+ * Statistics resource directives
+ *
+ * name handler value code flags default_value
+ */
+RES_ITEM collector_items[] = {
+ {"Name", store_name, ITEM(res_collector.hdr.name), 0, ITEM_REQUIRED, 0},
+ {"Description", store_str, ITEM(res_collector.hdr.desc), 0, 0, 0},
+ {"Prefix", store_str, ITEM(res_collector.prefix), 0, 0, 0},
+ {"Metrics", store_alist_str, ITEM(res_collector.metrics), 0, 0, 0}, /* default all */
+ {"Interval", store_time, ITEM(res_collector.interval), 0, ITEM_DEFAULT, 5*60}, /* default 5 min */
+ {"Port", store_pint32, ITEM(res_collector.port), 0, 0, 0},
+ {"Host", store_str, ITEM(res_collector.host), 0, 0, 0},
+ {"Type", store_coll_type, ITEM(res_collector.type), 0, ITEM_REQUIRED, 0},
+ {"File", store_str, ITEM(res_collector.file), 0, 0, 0},
+ {"MangleMetric", store_bool, ITEM(res_collector.mangle_name), 0, 0, 0},
+ {NULL, NULL, {0}, 0, 0, 0}
+};
+
/* Various message types */
s_kw msg_types[] = {
{"Debug", M_DEBUG}, /* Keep 1st place */
};
+/*
+ * Keywords (RHS) permitted in Statistics type records
+ *
+ * type_name backend_type
+ */
+s_collt collectortypes[] = {
+ {"CSV", COLLECTOR_BACKEND_CSV},
+ {"Graphite", COLLECTOR_BACKEND_Graphite},
+ {NULL, 0}
+};
+
+
/* Simply print a message */
static void prtmsg(void *sock, const char *fmt, ...)
{
{
int i;
int rindex = type - r_first;
-
+
memset(config->m_res_all, 0, config->m_res_all_size);
res_all.hdr.rcode = type;
res_all.hdr.refcnt = 1;
set_bit(index, res_all.hdr.item_present);
}
+/*
+ * Store Statistics Type (CSV, Graphite - only supported)
+ */
+void store_coll_type(LEX *lc, RES_ITEM *item, int index, int pass)
+{
+ int i;
+
+ lex_get_token(lc, T_NAME);
+ /* Store the type both pass 1 and pass 2 */
+ for (i = 0; collectortypes[i].type_name; i++) {
+ if (strcasecmp(lc->str, collectortypes[i].type_name) == 0) {
+ *(int32_t *)(item->value) = collectortypes[i].coll_type;
+ i = 0;
+ break;
+ }
+ }
+ if (i != 0) {
+ scan_err1(lc, _("Expected a Statistics backend type keyword, got: %s"), lc->str);
+ }
+ scan_to_eol(lc);
+ set_bit(index, res_all.hdr.item_present);
+}
/* Parser state */
enum parse_state {
Public License, v3.0 ("AGPLv3") and some additional permissions and
terms pursuant to its AGPLv3 Section 7.
- This notice must be preserved when any source code is
+ This notice must be preserved when any source code is
conveyed and/or propagated.
Bacula(R) is a registered trademark of Kern Sibbald.
int token;
};
+/* Collector Type keyword structure */
+struct s_collt {
+ const char *type_name;
+ int32_t coll_type;
+};
+
struct RES_ITEM; /* Declare forward referenced structure */
struct RES_ITEM1;
struct RES_ITEM2; /* Declare forward referenced structure */
inline char *MSGS::name() const { return hdr.name; }
+/* just for reference */
+class bstatcollect;
+
+/* Statistics Resource */
+class COLLECTOR {
+public:
+ RES hdr; /* standard resource header */
+ char *file; /* stat file if required */
+ char *prefix; /* metric prefix */
+ const char *daemon; /* the Daemon type string for spooling */
+ const char *spool_directory; /* a working directory of the Daemon */
+ utime_t interval; /* interval in seconds between metrics collection */
+ uint32_t port; /* TCP port when using network backend */
+ char *host; /* remote host address when using network backend */
+ int32_t type; /* the Collector backend type */
+ alist *metrics; /* the list for Metrics parameters in resource */
+ /* private */
+ JCR *jcr; /* JCR resource */
+ bstatcollect *statcollector; /* the reference to daemon's bstatcollect'or class */
+ time_t timestamp; /* the last collection time */
+ bool valid; /* when set to false the collector thread should involuntary exit */
+ bool running; /* set when a collector thread is running */
+ bool mangle_name; /* when set metrics name will be mangled by replacing dot '.' for "%32" */
+ int spooled; /* the spooling status of the collector */
+ POOLMEM *errmsg; /* error message if any */
+ pthread_t thid; /* thread id for collector thread */
+ pthread_mutex_t mutex; /* when accessing collector resource data you should lock it first */
+
+public:
+ /* Methods */
+ char *name() const;
+ void lock(); /* in bcollector.c */
+ void unlock(); /* in bcollector.c */
+ void setspooled(int s); /* in bcollector.c */
+ int getspooled(); /* in bcollector.c */
+ void updatetimestamp(); /* in bcollector.c */
+};
+
+inline char *COLLECTOR::name() const { return hdr.name; }
+
/*
* New C++ configuration routines
*/
/*
- * Standard global parsers defined in parse_config.c
+ * Standard global parsers defined in parse_config.c and bcollector.c
*/
void store_str(LEX *lc, RES_ITEM *item, int index, int pass);
void store_dir(LEX *lc, RES_ITEM *item, int index, int pass);
void store_speed(LEX *lc, RES_ITEM *item, int index, int pass);
void store_defs(LEX *lc, RES_ITEM *item, int index, int pass);
void store_label(LEX *lc, RES_ITEM *item, int index, int pass);
+void store_coll_type(LEX *lc, RES_ITEM *item, int index, int pass);
/* ***FIXME*** eliminate these globals */
extern int32_t r_first;
void init_hpkt(HPKT &hpkt);
void term_hpkt(HPKT &hpkt);
bool display_global_item(HPKT &hpkt);
+void display_collector_types(HPKT &hpkt);
/* bsys.c */
char *ucfirst(char *dest, const char *src, int len);
# bacula-sd
SDOBJS = \
stored.o append.o authenticate.o dircmd.o fd_cmds.o job.o \
- hello.o status.o vbackup.o \
+ hello.o status.o vbackup.o sdcollect.o \
$(SDCORE_OBJS)
JSONOBJS = bsdjson.o stored_conf.o
display_truncate_cache(hpkt);
} else if (items[item].handler == store_upload) {
display_upload(hpkt);
+ } else if (items[item].handler == store_coll_type) {
+ display_collector_types(hpkt);
} else {
printf("\n \"%s\": \"null\"", items[item].name);
}
static const int dbglvl = 150;
-/* Imported functions */
+/* Imported functions and variables */
extern void set_os_device_parameters(DCR *dcr);
extern bool dev_get_os_pos(DEVICE *dev, struct mtget *mt_stat);
extern uint32_t status_dev(DEVICE *dev);
ssize_t DEVICE::read(void *buf, size_t len)
{
ssize_t read_len;
+ ssize_t stat_read_len = 0;
get_timer_count();
if (read_len > 0) { /* skip error */
DevReadBytes += read_len;
+ stat_read_len = read_len;
}
+ collector_update_add2_value_int64(devstatcollector, devstatmetrics.bacula_storage_device_readbytes, stat_read_len,
+ devstatmetrics.bacula_storage_device_readtime, last_tick);
+
return read_len;
}
ssize_t DEVICE::write(const void *buf, size_t len)
{
ssize_t write_len;
+ ssize_t stat_write_len = 0;
get_timer_count();
if (write_len > 0) { /* skip error */
DevWriteBytes += write_len;
+ stat_write_len = write_len;
}
+ collector_update_add2_value_int64(devstatcollector, devstatmetrics.bacula_storage_device_writebytes, stat_write_len,
+ devstatmetrics.bacula_storage_device_writetime, last_tick);
+
return write_len;
}
return 0;
}
+// TODO: fill metrics description
+void DEVICE::register_metrics(bstatcollect *collector)
+{
+ POOL_MEM met(PM_NAME);
+
+ devstatcollector = collector;
+ if (!collector){
+ return;
+ }
+ Dmsg2(100, "DEVICE::register_metrics called. 0x%p collector=0x%p\n", this, collector);
+ Mmsg(met, "bacula.storage.%s.device.%s.readbytes", me->hdr.name, name());
+ devstatmetrics.bacula_storage_device_readbytes =
+ devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ (char*)"The number of bytes read from device.");
+ Mmsg(met, "bacula.storage.%s.device.%s.readtime", me->hdr.name, name());
+ devstatmetrics.bacula_storage_device_readtime =
+ devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_MSEC,
+ (char*)"Time spent reading from device.");
+ Mmsg(met, "bacula.storage.%s.device.%s.readspeed", me->hdr.name, name());
+ devstatmetrics.bacula_storage_device_readspeed =
+ devstatcollector->registration(met.c_str(), METRIC_FLOAT, METRIC_UNIT_BYTESEC,
+ (char*)"Device read throughput.");
+ Mmsg(met, "bacula.storage.%s.device.%s.writespeed", me->hdr.name, name());
+ devstatmetrics.bacula_storage_device_writespeed =
+ devstatcollector->registration(met.c_str(), METRIC_FLOAT, METRIC_UNIT_BYTESEC,
+ (char*)"Device write throughput.");
+ Mmsg(met, "bacula.storage.%s.device.%s.status", me->hdr.name, name());
+ devstatmetrics.bacula_storage_device_status =
+ devstatcollector->registration_bool(met.c_str(), METRIC_UNIT_STATUS, enabled,
+ (char*)"Show if device is enabled (True/1) or disabled (False/0).");
+ Mmsg(met, "bacula.storage.%s.device.%s.writebytes", me->hdr.name, name());
+ devstatmetrics.bacula_storage_device_writebytes =
+ devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ (char*)"The number of bytes written to device.");
+ Mmsg(met, "bacula.storage.%s.device.%s.writetime", me->hdr.name, name());
+ devstatmetrics.bacula_storage_device_writetime =
+ devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_MSEC,
+ (char*)"Time spent writting to device.");
+ /* total and free space metrics registration */
+ Mmsg(met, "bacula.storage.%s.device.%s.freespace", me->hdr.name, name());
+ devstatmetrics.bacula_storage_device_freespace =
+ devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ (char*)"The size of available space of the disk storage for device (could be shared).");
+ Mmsg(met, "bacula.storage.%s.device.%s.totalspace", me->hdr.name, name());
+ devstatmetrics.bacula_storage_device_totalspace =
+ devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ (char*)"The size of the disk storage for device (could be shared).");
+};
+
bool DEVICE::get_tape_worm(DCR *dcr)
{
return false;
}
+
const char *long_msg, char *Volume, int severity,
int flags, int alert, utime_t alert_time);
+/* registered device metrics */
+typedef struct {
+ int bacula_storage_device_freespace;
+ int bacula_storage_device_totalspace;
+ int bacula_storage_device_readbytes;
+ int bacula_storage_device_readtime;
+ int bacula_storage_device_readspeed;
+ int bacula_storage_device_writespeed;
+ int bacula_storage_device_status;
+ int bacula_storage_device_writebytes;
+ int bacula_storage_device_writetime;
+} devstatmetrics_t;
+
/* Aligned Data Disk Volume extension */
#define ADATA_EXTENSION ".add"
int rem_wait_sec;
int num_wait;
- btime_t last_timer; /* used by read/write/seek to get stats (usec) */
- btime_t last_tick; /* contains last read/write time (usec) */
+ btime_t last_timer; /* used by read/write/seek to get stats (usec) */
+ btime_t last_tick; /* contains last read/write time (usec) */
+ utime_t last_stat_timer; /* used by update_permanent_stats() to count throughput */
btime_t DevReadTime;
btime_t DevWriteTime;
uint64_t DevReadBytes;
uint64_t usage; /* Drive usage read+write bytes */
+ uint64_t last_stat_DevWriteBytes;
+ uint64_t last_stat_DevReadBytes;
+
+ devstatmetrics_t devstatmetrics; /* these are a device metrics for every device */
+ bstatcollect *devstatcollector; /* a pointer to daemon's statcollector */
+
/* Methods */
btime_t get_timer_count(); /* return the last timer interval (ms) */
virtual void select_data_stream(DCR *dcr, DEV_RECORD *rec) { return; };
virtual bool flush_block(DCR *dcr); /* in block_util.c */
virtual bool do_pre_write_checks(DCR *dcr, DEV_RECORD *rec) { return true; };
-
+ virtual void register_metrics(bstatcollect *collector);
/*
* Locking and blocking calls
extern bool run_cmd(JCR *jcr);
extern bool status_cmd(JCR *sjcr);
extern bool qstatus_cmd(JCR *jcr);
+extern bool collect_cmd(JCR *jcr);
//extern bool query_cmd(JCR *jcr);
/* Forward referenced functions */
{"unmount", unmount_cmd, 0},
{"use storage=", use_cmd, 0},
{"run", run_cmd, 0},
+ {"statistics", collect_cmd, 0},
// {"query", query_cmd, 0},
{NULL, NULL} /* list terminator */
};
if ((bnet_stat = bs->recv()) <= 0) {
break; /* connection terminated */
}
- Dmsg1(199, "<dird: %s", bs->msg);
+ Dmsg1(199, "<dird: %s\n", bs->msg);
/* Ensure that device initialization is complete */
while (!init_done) {
bmicrosleep(1, 0);
if (strcmp(device->hdr.name, devname.c_str()) == 0 &&
(!media_type || strcmp(device->media_type, media_type) ==0)) {
if (!device->dev) {
- device->dev = init_dev(jcr, device);
+ device->dev = init_dev(jcr, device, false, statcollector);
}
if (!device->dev) {
Jmsg(jcr, M_WARNING, 0, _("\n"
foreach_alist(device, changer->device) {
Dmsg1(100, "Try changer device %s\n", device->hdr.name);
if (!device->dev) {
- device->dev = init_dev(jcr, device);
+ device->dev = init_dev(jcr, device, false, statcollector);
}
if (!device->dev) {
Dmsg1(100, "Device %s could not be opened. Skipped\n", devname.c_str());
if (strcmp(device->hdr.name, devname.c_str()) == 0 &&
(!media_type || strcmp(device->media_type, media_type) ==0)) {
if (!device->dev) {
- device->dev = init_dev(jcr, device);
+ device->dev = init_dev(jcr, device, false, statcollector);
}
if (!device->dev) {
Jmsg(jcr, M_WARNING, 0, _("\n"
foreach_alist(device, changer->device) {
Dmsg1(100, "Try changer device %s\n", device->hdr.name);
if (!device->dev) {
- device->dev = init_dev(jcr, device);
+ device->dev = init_dev(jcr, device, false, statcollector);
}
if (!device->dev) {
Dmsg1(100, "Device %s could not be opened. Skipped\n", devname.c_str());
dir->fsend(_("3003 Device \"%s\" already enabled.\n"), dev->print_name());
} else {
dev->enabled = true;
+ dev->devstatcollector->set_value_bool(dev->devstatmetrics.bacula_storage_device_status, true);
dir->fsend(_("3002 Device \"%s\" enabled.\n"), dev->print_name());
}
deleted = dev->delete_alerts();
dev = dcr->dev;
dev->Lock();
dev->enabled = false;
+ dev->devstatcollector->set_value_bool(dev->devstatmetrics.bacula_storage_device_status, false);
dir->fsend(_("3002 Device \"%s\" disabled.\n"), dev->print_name());
dev->Unlock();
free_dcr(dcr);
* is the directory in which the file will be placed.
*
*/
-DEVICE *init_dev(JCR *jcr, DEVRES *device, bool adata)
+DEVICE *init_dev(JCR *jcr, DEVRES *device, bool adata, bstatcollect *statcollector)
{
struct stat statp;
DEVICE *dev = NULL;
if (!dev) {
return NULL;
}
-
+ Dmsg1(100, "init_dev allocated: %p\n", dev);
dev->adata = adata;
/* Keep the device ID in the DEVICE struct to identify the hardware */
dev->capabilities |= CAP_STREAM; /* set stream device */
}
+ dev->register_metrics(statcollector);
+
return dev;
}
/* Find resource, and make sure we were able to open it */
if (strcmp(dev_name.c_str(), device->hdr.name) == 0) {
if (!device->dev) {
- device->dev = init_dev(jcr, device);
+ device->dev = init_dev(jcr, device, false, statcollector);
}
if (!device->dev) {
break;
/* From dev.c */
void sd_list_loaded_drivers(alist *list);
-DEVICE *init_dev(JCR *jcr, DEVRES *device, bool adata=false);
+DEVICE *init_dev(JCR *jcr, DEVRES *device, bool adata=false, bstatcollect *statcollector=NULL);
bool can_open_mounted_dev(DEVICE *dev);
bool load_dev(DEVICE *dev);
int write_block(DEVICE *dev);
void store_devtype(LEX *lc, RES_ITEM *item, int index, int pass);
void store_cloud_driver(LEX *lc, RES_ITEM *item, int index, int pass);
void store_maxblocksize(LEX *lc, RES_ITEM *item, int index, int pass);
+
+/* from sdcollect.c */
+bool update_permanent_stats(void *data);
+void initialize_statcollector();
+void start_collector_threads();
+void terminate_collector_threads();
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2017 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Radosław Korzeniewski, MMXVIII
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * This is a Bacula statistics Storage Daemon collector routines.
+ * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
+ *
+ */
+
+#include "bacula.h"
+#include "stored.h"
+
+/* imported functions and variables */
+extern void *start_heap;
+extern const char collect_all_cmd[];
+extern const char collect_metrics_cmd[];
+extern bool init_done;
+
+static bool collector_threads_started = false;
+
+/*
+ * Initialize a daemon wide Bacula statcollector.
+ * Creates a global daemon collector and registers all global metrics.
+ * This is a statcollector initialization function specific for StorageDaemon.
+ */
+void initialize_statcollector()
+{
+ POOL_MEM met(PM_NAME);
+
+ /* create a statistics collector */
+ statcollector = New(bstatcollect);
+ /* register config metrics */
+ Mmsg(met, "bacula.storage.%s.config.devices", me->hdr.name);
+ sdstatmetrics.bacula_storage_config_devices =
+ statcollector->registration_int64(met.c_str(), METRIC_UNIT_DEVICE,
+ ((rblist *)res_head[R_DEVICE-r_first]->res_list)->size(),
+ "The number of defined devices in Storage.");
+ Mmsg(met, "bacula.storage.%s.config.autochangers", me->hdr.name);
+ sdstatmetrics.bacula_storage_config_autochangers =
+ statcollector->registration_int64(met.c_str(), METRIC_UNIT_AUTOCHANGER,
+ ((rblist *)res_head[R_AUTOCHANGER-r_first]->res_list)->size(),
+ "The number of defined autochangers in Storage.");
+ /* memory utilization */
+ Mmsg(met, "bacula.storage.%s.memory.bufs", me->hdr.name);
+ sdstatmetrics.bacula_storage_memory_bufs =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_NUMBER,
+ "The number of buffers allocated.");
+ Mmsg(met, "bacula.storage.%s.memory.heap", me->hdr.name);
+ sdstatmetrics.bacula_storage_memory_heap =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ "The size of the heap.");
+ Mmsg(met, "bacula.storage.%s.memory.maxbufs", me->hdr.name);
+ sdstatmetrics.bacula_storage_memory_maxbufs =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_NUMBER,
+ "The maximum buffers allocated.");
+ Mmsg(met, "bacula.storage.%s.memory.maxbytes", me->hdr.name);
+ sdstatmetrics.bacula_storage_memory_maxbytes =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ "The allocated memory size.");
+ Mmsg(met, "bacula.storage.%s.memory.smbytes", me->hdr.name);
+ sdstatmetrics.bacula_storage_memory_smbytes =
+ statcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
+ "The allocated memory size.");
+ // statcollector->dump();
+};
+
+/*
+ * This is a callback function executed by updatecollector_thread() every time some permanent and hard to count
+ * metrics should be collected. This include a memory allocator statistics and DEVICE throughput.
+ */
+bool update_permanent_stats(void *data)
+{
+ DEVRES *device;
+ DEVICE *dev;
+ float speed;
+ utime_t t;
+ utime_t delta;
+ uint64_t rd;
+ uint64_t wd;
+ uint64_t fs, ts;
+
+ /* update memory statistics */
+ statcollector->set_value_int64(sdstatmetrics.bacula_storage_memory_bufs, sm_buffers);
+#ifdef HAVE_WIN32
+ uint64_t memused;
+ memused = get_memory_info(NULL, 0);
+ statcollector->set_value_int64(sdstatmetrics.bacula_storage_memory_heap, memused);
+#else
+ statcollector->set_value_int64(sdstatmetrics.bacula_storage_memory_heap, (char *)sbrk(0)-(char *)start_heap);
+#endif
+ statcollector->set_value_int64(sdstatmetrics.bacula_storage_memory_maxbufs, sm_max_buffers);
+ statcollector->set_value_int64(sdstatmetrics.bacula_storage_memory_maxbytes, sm_max_bytes);
+ statcollector->set_value_int64(sdstatmetrics.bacula_storage_memory_smbytes, sm_bytes);
+ /* update device throughput */
+ t = time(NULL);
+ LockRes();
+ if (init_done){
+ foreach_res(device, R_DEVICE) {
+ dev = device->dev;
+ if (!dev){
+ continue;
+ }
+ rd = statcollector->get_int(dev->devstatmetrics.bacula_storage_device_readbytes);
+ wd = statcollector->get_int(dev->devstatmetrics.bacula_storage_device_writebytes);
+ if (dev->last_stat_timer > 0){
+ /* count throughput */
+ delta = t - dev->last_stat_timer;
+ if (delta > 0){
+ /* only when a time passed */
+ speed = ((float)rd - dev->last_stat_DevReadBytes) / (float)delta;
+ statcollector->set_value_float(dev->devstatmetrics.bacula_storage_device_readspeed, speed);
+ speed = ((float)wd - dev->last_stat_DevWriteBytes) / (float)delta;
+ statcollector->set_value_float(dev->devstatmetrics.bacula_storage_device_writespeed, speed);
+ }
+ }
+ dev->last_stat_timer = t;
+ dev->last_stat_DevReadBytes = rd;
+ dev->last_stat_DevWriteBytes = wd;
+ /* count total/free space */
+ dev->get_freespace(&fs, &ts);
+ statcollector->set_value_int64(dev->devstatmetrics.bacula_storage_device_freespace, fs);
+ statcollector->set_value_int64(dev->devstatmetrics.bacula_storage_device_totalspace, ts);
+ };
+ }
+ UnlockRes();
+ return true;
+};
+
+/*
+ * Starts a single thread for every defined Statistics resource and one globally single thread for
+ * Update Statistics if at least one Statistics resource is available.
+ */
+void start_collector_threads()
+{
+ COLLECTOR *collect;
+ utime_t interval = 24*3600; // the minimal update interval is 1 day
+ UPDATE_COLLECTOR_INIT_t initdata;
+
+ LockRes();
+ foreach_res(collect, R_COLLECTOR){
+ interval = MIN(interval, collect->interval);
+ collect->statcollector = statcollector;
+ collect->spool_directory = working_directory;
+ collect->daemon = "bacula-sd";
+ collect->jcr = new_jcr(sizeof(JCR), stored_free_jcr);
+ Dmsg1(100, "Starting statistics thread for \"%s\"\n", collect->name());
+ start_collector_thread(collect);
+ collector_threads_started = true;
+ };
+ if (((rblist *)res_head[R_COLLECTOR-r_first]->res_list)->size() > 0){
+ initdata.interval = interval;
+ initdata.jcr = new_jcr(sizeof(JCR), stored_free_jcr);
+ initdata.routine = &update_permanent_stats;
+ initdata.data = NULL;
+ /* start update thread */
+ Dmsg0(100, "Starting update collector thread.\n");
+ start_updcollector_thread(initdata);
+ }
+ UnlockRes();
+};
+
+/*
+ * Terminates all Statistics's threads executed.
+ */
+void terminate_collector_threads()
+{
+ COLLECTOR *collect;
+
+ if (collector_threads_started){
+ foreach_res(collect, R_COLLECTOR){
+ stop_collector_thread(collect);
+ };
+ stop_updcollector_thread();
+ collector_threads_started = false;
+ }
+};
+
+/*
+ * Display a text representation of the metric for UA.
+ *
+ * in:
+ * user - a BSOCK class for sending rendered text to UA
+ * m - metric to display
+ * format - a display format
+ * out:
+ * metric rendered and sent to the UA
+ */
+void display_metric(BSOCK *user, bstatmetric *m, display_format_t format, int nr)
+{
+ POOL_MEM out(PM_MESSAGE);
+ int len;
+
+ rendermetric(out, m, format, nr);
+ len = strlen(out.c_str()) + 1;
+ user->msg = check_pool_memory_size(user->msg, len);
+ memcpy(user->msg, out.c_str(), len);
+ user->msglen = len;
+ user->send();
+};
+
+/*
+ * Collect command from Director
+ */
+bool collect_cmd(JCR *jcr)
+{
+ BSOCK *dir = jcr->dir_bsock;
+ POOLMEM *cmd;
+ POOLMEM *fmt;
+ POOLMEM *metrics = NULL;
+ char *ch;
+ char *met;
+ bool doall = true;
+ display_format_t format;
+ bstatmetric *item;
+ alist *list;
+ int nr;
+
+ cmd = get_memory(dir->msglen+1);
+ fmt = get_memory(dir->msglen+1);
+
+ Dmsg1(100, "statistics cmd: %s", dir->msg);
+ if (sscanf(dir->msg, collect_all_cmd, fmt) != 1) {
+ if (sscanf(dir->msg, collect_metrics_cmd, fmt, cmd) != 2) {
+ pm_strcpy(&jcr->errmsg, dir->msg);
+ Jmsg1(jcr, M_FATAL, 0, _("Bad collect command: %s\n"), jcr->errmsg);
+ dir->fsend(_("2900 Bad statistics command, missing argument.\n"));
+ dir->signal(BNET_EOD);
+ free_memory(cmd);
+ free_memory(fmt);
+ return false;
+ } else {
+ doall = false;
+ metrics = strstr(dir->msg, cmd);
+ pm_strcpy(cmd, metrics);
+ metrics = cmd;
+ }
+ }
+
+ format = scandisplayformat(fmt);
+ if (!doall) Dmsg1(100, "statistics metrics: %s\n", metrics);
+ if (format == COLLECT_JSON){
+ dir->fsend("[");
+ }
+ update_permanent_stats(NULL);
+ nr = 0;
+ if (doall){
+ /* we should display all the metrics found */
+ list = statcollector->get_all();
+ foreach_alist(item, list){
+ display_metric(dir, item, format, nr++);
+ }
+ free_metric_alist(list);
+ } else {
+ /* we should display selected metrics only */
+ met = metrics;
+ /* eliminate the EOL char in metrics */
+ ch = strchr(met, '\n');
+ if (ch){
+ *ch = ' ';
+ }
+ while (*met != 0 && (ch = strchr(met, ' ')) != NULL){
+ /* prepare and get a single metric */
+ *ch = '\0';
+ Dmsg1(100, "statistics get metric: %s\n", met);
+ item = statcollector->get_metric(met);
+ if (item){
+ Dmsg0(200, "metric found\n");
+ display_metric(dir, item, format, nr++);
+ delete(item);
+ }
+ met = ch + 1;
+ }
+ }
+ if (format == COLLECT_JSON){
+ dir->fsend("\n]\n");
+ }
+
+ dir->signal(BNET_EOD);
+ free_memory(cmd);
+ free_memory(fmt);
+ return true;
+}
static void list_devices(STATUS_PKT *sp, char *name=NULL);
static void list_plugins(STATUS_PKT *sp);
static void list_cloud_transfers(STATUS_PKT *sp, bool verbose);
+static void list_collectors_status(STATUS_PKT *sp, char *collname);
+static void api_collectors_status(STATUS_PKT *sp, char *collname);
void status_alert_callback(void *ctx, const char *short_msg,
const char *long_msg, char *Volume, int severity,
bool ret=true;
char *cmd;
char *device=NULL;
+ char *collname=NULL;
int api = true;
sp.bs = dir;
} else if (!strcmp(argk[i], "api") && argv[i]) {
api = atoi(argv[i]);
+ } else if (!strcmp(argk[i], "statistics") && argv[i]) {
+ collname = argv[i];
+ unbash_spaces(collname);
+
} else if (!strcmp(argk[i], "api_opts") && argv[i]) {
- strncpy(sp.api_opts, argv[i], sizeof(sp.api_opts));;
+ strncpy(sp.api_opts, argv[i], sizeof(sp.api_opts));
}
}
list_resources(&sp);
} else if (strcasecmp(cmd, "cloud") == 0) {
list_cloud_transfers(&sp, true);
+ } else if (strcasecmp(cmd, "statistics") == 0) {
+ sp.api = api;
+ list_collectors_status(&sp, collname);
} else {
pm_strcpy(jcr->errmsg, dir->msg);
dir->fsend(_("3900 Unknown arg in .status command: %s\n"), jcr->errmsg);
sendit(msg.c_str(), len, sp);
}
}
+
+static void list_collectors_status(STATUS_PKT *sp, char *collname)
+{
+ URES *res;
+ int len;
+ POOL_MEM buf(PM_MESSAGE);
+
+ Dmsg2(200, "enter list_collectors_status() api=%i coll=%s\n", sp->api, NPRTB(collname));
+ if (sp->api > 1) {
+ api_collectors_status(sp, collname);
+ return;
+ }
+
+ LockRes();
+ foreach_res(res, R_COLLECTOR) {
+ if (collname && !bstrcmp(collname, res->res_collector.hdr.name)){
+ continue;
+ }
+ Dmsg1(500, "processing: %s\n", res->res_collector.hdr.name);
+ len = render_collector_status(res->res_collector, buf);
+ sendit(buf.c_str(), len, sp);
+ };
+ UnlockRes();
+ if (!collname){
+ len = render_updcollector_status(buf);
+ sendit(buf.c_str(), len, sp);
+ }
+ Dmsg0(200, "leave list_collectors_status()\n");
+}
+
+static void api_collectors_status(STATUS_PKT *sp, char *collname)
+{
+ URES *res;
+ OutputWriter ow(sp->api_opts);
+ POOLMEM *buf;
+
+ Dmsg1(200, "enter api_collectors_status() %s\n", NPRTB(collname));
+ ow.start_group("collector_backends");
+ LockRes();
+ foreach_res(res, R_COLLECTOR) {
+ if (collname && !bstrcmp(collname, res->res_collector.hdr.name)){
+ continue;
+ }
+ Dmsg1(500, "processing: %s\n", res->res_collector.hdr.name);
+ api_render_collector_status(res->res_collector, ow);
+ };
+ UnlockRes();
+ buf = ow.end_group();
+ if (!collname){
+ ow.start_group("collector_update");
+ api_render_updcollector_status(ow);
+ buf = ow.end_group();
+ }
+ sendit(buf, strlen(buf), sp);
+ Dmsg0(200, "leave api_collectors_status()\n");
+};
char TERM_msg[] = "3999 Terminate\n";
void *start_heap;
static bool test_config = false;
-
+bstatcollect *statcollector = NULL;
+sdstatmetrics_t sdstatmetrics;
static uint32_t VolSessionId = 0;
uint32_t VolSessionTime;
static workq_t dird_workq; /* queue for processing connections */
static CONFIG *config;
-
static void usage()
{
fprintf(stderr, _(
drop(uid, gid, false);
+ /* initialize a statistics collector */
+ initialize_statcollector();
+
cleanup_old_files();
/* Ensure that Volume Session Time and Id are both
init_jcr_subsystem(); /* start JCR watchdogs etc. */
dbg_jcr_add_hook(sd_debug_print); /* used to director variables */
+ start_collector_threads(); /* start collector thread for every Collector resource */
+
/* Single server used for Director and File daemon */
server_tid = pthread_self();
server_tid_valid = true;
jcr = new_jcr(sizeof(JCR), stored_free_jcr);
new_plugins(jcr); /* instantiate plugins */
jcr->setJobType(JT_SYSTEM);
- /* Initialize FD start condition variable */
+ /* Initialize SD start condition variable */
int errstat = pthread_cond_init(&jcr->job_start_wait, NULL);
if (errstat != 0) {
berrno be;
foreach_res(device, R_DEVICE) {
Dmsg1(90, "calling init_dev %s\n", device->hdr.name);
- dev = init_dev(NULL, device);
- Dmsg1(10, "SD init done %s\n", device->hdr.name);
+ dev = init_dev(NULL, device, false, statcollector);
+ Dmsg2(10, "SD init done %s (0x%p)\n", device->hdr.name, dev);
if (!dev) {
Jmsg1(NULL, M_ERROR, 0, _("Could not initialize SD device \"%s\"\n"), device->hdr.name);
continue;
- }
-
+ }
jcr->dcr = dcr = new_dcr(jcr, NULL, dev);
generate_plugin_event(jcr, bsdEventDeviceInit, dcr);
break;
}
}
-
free_dcr(dcr);
jcr->dcr = NULL;
}
in_here = true;
debug_level = 0; /* turn off any debug */
stop_watchdog();
+ terminate_collector_threads();
if (sig == SIGTERM || sig == SIGINT) { /* normal shutdown request? or ^C */
/*
if (chk_dbglvl(10)) {
print_memory_pool_stats();
}
+ if (statcollector){
+ // statcollector->dump();
+ delete(statcollector);
+ }
term_msg();
cleanup_crypto();
term_reservations_lock();
#include "win_tape_dev.h"
#include "sd_plugins.h"
+typedef struct {
+ int bacula_storage_config_devices;
+ int bacula_storage_config_autochangers;
+ int bacula_storage_memory_bufs;
+ int bacula_storage_memory_heap;
+ int bacula_storage_memory_maxbufs;
+ int bacula_storage_memory_maxbytes;
+ int bacula_storage_memory_smbytes;
+} sdstatmetrics_t;
+
int breaddir(DIR *dirp, POOLMEM *&d_name);
/* Daemon globals from stored.c */
extern bool forge_on; /* proceed inspite of I/O errors */
extern pthread_mutex_t device_release_mutex;
extern pthread_cond_t wait_device_release; /* wait for any device to be released */
+extern bool update_permanent_stats(void *data);
+extern bstatcollect *statcollector;
+extern sdstatmetrics_t sdstatmetrics;
#endif /* __STORED_H_ */
/* Message resource */
extern RES_ITEM msgs_items[];
+/* Statistics resource */
+extern RES_ITEM collector_items[];
+
/* This is the master resource definition */
RES_TABLE resources[] = {
- {"Director", dir_items, R_DIRECTOR},
- {"Storage", store_items, R_STORAGE},
- {"Device", dev_items, R_DEVICE},
- {"Messages", msgs_items, R_MSGS},
- {"Autochanger", changer_items, R_AUTOCHANGER},
- {"Cloud", cloud_items, R_CLOUD},
- {NULL, NULL, 0}
+ {"Director", dir_items, R_DIRECTOR},
+ {"Storage", store_items, R_STORAGE},
+ {"Device", dev_items, R_DEVICE},
+ {"Messages", msgs_items, R_MSGS},
+ {"Autochanger", changer_items, R_AUTOCHANGER},
+ {"Cloud", cloud_items, R_CLOUD},
+ {"Statistics", collector_items, R_COLLECTOR},
+ {NULL, NULL, 0}
};
/*
if (res->res_msgs.operator_cmd)
sendit(sock, " opcmd=%s\n", res->res_msgs.operator_cmd);
break;
+ case R_COLLECTOR:
+ dump_collector_resource(res->res_collector, sendit, sock);
+ break;
default:
sendit(sock, _("Warning: unknown resource type %d\n"), type);
break;
free_msgs_res((MSGS *)res); /* free message resource */
res = NULL;
break;
+ case R_COLLECTOR:
+ free_collector_resource(res->res_collector);
+ break;
default:
Dmsg1(0, _("Unknown resource type %d\n"), type);
break;
}
res->res_dev.cloud = res_all.res_dev.cloud;
break;
+ case R_COLLECTOR:
+ if ((res = (URES *)GetResWithName(R_COLLECTOR, res_all.res_collector.hdr.name)) == NULL) {
+ Mmsg(config->m_errmsg, _("Cannot find Statistics resource %s\n"), res_all.res_collector.hdr.name);
+ return false;
+ }
+ res->res_collector.metrics = res_all.res_collector.metrics;
+ // Dmsg2(100, "metrics = 0x%p 0x%p\n", res->res_collector.metrics, res_all.res_collector.metrics);
+ break;
default:
printf(_("Unknown resource type %d\n"), type);
error = 1;
case R_CLOUD:
size = sizeof(CLOUD);
break;
+ case R_COLLECTOR:
+ size = sizeof(COLLECTOR);
+ break;
default:
printf(_("Unknown resource type %d\n"), type);
error = 1;
R_MSGS = 3004,
R_AUTOCHANGER = 3005,
R_CLOUD = 3006,
+ R_COLLECTOR = 3007,
R_FIRST = R_DIRECTOR,
- R_LAST = R_CLOUD /* keep this updated */
+ R_LAST = R_COLLECTOR /* keep this updated */
};
enum {
AUTOCHANGER res_changer;
CLOUD res_cloud;
RES hdr;
+ COLLECTOR res_collector;
};
--- /dev/null
+#
+# Default Bacula Director Configuration file
+#
+# The only thing that MUST be changed is to add one or more
+# file or directory names in the Include directive of the
+# FileSet resource.
+#
+# For Bacula release 1.39 or later
+#
+# You might also want to change the default email address
+# from root to your address. See the "mail" and "operator"
+# directives in the Messages resource.
+#
+
+Director { # define myself
+ Name = @hostname@-dir
+ DIRPort = @dirport@ # where we listen for UA connections
+ QueryFile = "@scriptdir@/query.sql"
+ WorkingDirectory = "@working_dir@"
+ PidDirectory = "@piddir@"
+ SubSysDirectory = "@subsysdir@"
+ PluginDirectory = "@sbindir@"
+ Maximum Concurrent Jobs = 4
+ Heartbeat Interval = 330
+ Password = "pNvX1WiXnwv2C/F7E52LGvw6rKjbbPvu2kyuPa9pVaL3" # Console password
+ Messages = Standard
+}
+
+#
+# Define the main nightly save backup job
+# By default, this job will back up to disk in @tmpdir@
+Job {
+ Name = "NightlySave"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="Full Set"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Maximum Concurrent Jobs = 10
+ SpoolData=yes
+ Max Run Time = 30min
+ Reschedule On Error = no
+ Reschedule Interval = 10
+ Reschedule Times = 5
+}
+
+
+#
+# Define the main nightly save backup job
+# By default, this job will back up to disk in @tmpdir@
+Job {
+ Name = "FSType"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="FSTypeFS"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Maximum Concurrent Jobs = 10
+ SpoolData=yes
+}
+
+Job {
+ Name = "Simple"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="SimpleSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Maximum Concurrent Jobs = 10
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+# SpoolData=yes
+}
+
+
+Job {
+ Name = "MonsterSave"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="Full Set"
+ Storage = File1
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+ Maximum Concurrent Jobs = 10
+ SpoolData=yes
+}
+
+Job {
+ Name = "MonsterFileSet"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="MonsterFileSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Maximum Concurrent Jobs = 10
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+ SpoolData=yes
+}
+
+
+
+Job {
+ Name = "VerifyVolume"
+ Type = Verify
+ Level = VolumeToCatalog
+ Client=@hostname@-fd
+ FileSet="Full Set"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+}
+
+Job {
+ Name = "VerifyData"
+ Type = Verify
+ Level = Data
+ Client=@hostname@-fd
+ FileSet="Full Set"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+}
+
+
+Job {
+ Name = "SparseTest"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="SparseSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+ SpoolData=yes
+ Maximum Concurrent Jobs = 10
+}
+
+Job {
+ Name = "DeltaTest"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="DeltaSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+ Maximum Concurrent Jobs = 10
+}
+
+Job {
+ Name = "CompressedTest"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="CompressedSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Maximum Concurrent Jobs = 10
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+ SpoolData=yes
+}
+
+Job {
+ Name = "HardlinkTest"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="HardlinkSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Maximum Concurrent Jobs = 10
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+ Accurate = yes
+}
+
+Job {
+ Name = "SparseCompressedTest"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="SparseCompressedSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+ SpoolData=yes
+ Maximum Concurrent Jobs = 10
+}
+
+Job {
+ Name = "vSphereTest"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="vSphereSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Maximum Concurrent Jobs = 10
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+}
+
+Job {
+ Name = "LZOTest"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="LZOSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Maximum Concurrent Jobs = 10
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+ SpoolData=yes
+}
+
+Job {
+ Name = "SparseLZOTest"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="SparseLZOSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ Max Run Time = 30min
+ SpoolData=yes
+ Maximum Concurrent Jobs = 10
+}
+
+Job {
+ Name = "FIFOTest"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="FIFOSet"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Write Bootstrap = "@working_dir@/NightlySave.bsr"
+ ClientRunBeforeJob = "sleep 5"
+ Max Run Time = 30min
+}
+
+
+
+# Backup the catalog database (after the nightly save)
+Job {
+ Name = "BackupCatalog"
+ Type = Backup
+ Client=@hostname@-fd
+ FileSet="Catalog"
+# Schedule = "WeeklyCycleAfterBackup"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ # This creates an ASCII copy of the catalog
+ RunBeforeJob = "@sbindir@/make_catalog_backup -u regress"
+ # This deletes the copy of the catalog
+ RunAfterJob = "@sbindir@/delete_catalog_backup"
+ Write Bootstrap = "@working_dir@/BackupCatalog.bsr"
+ Max Run Time = 30min
+}
+
+JobDefs {
+ Name = "BackupJob"
+ Type = Backup
+ Pool = Default
+ Storage = File
+ Messages = Standard
+ Priority = 10
+ Max Run Time = 30min
+}
+
+Job {
+ JobDefs = "BackupJob"
+ Name = "bug621-job-1"
+ Client = @hostname@-fd
+ FileSet="Full Set"
+ ClientRunBeforeJob = "sleep 5"
+}
+
+Job {
+ JobDefs = "BackupJob"
+ Name = "bug621-job-2"
+ Client = @hostname@-fd
+ FileSet = "Full Set"
+ Max Run Time = 30
+ Priority = 15
+}
+
+
+# Standard Restore template, to be changed by Console program
+Job {
+ Name = "RestoreFiles"
+ Type = Restore
+ Client=@hostname@-fd
+ FileSet="Full Set"
+ Storage = File
+ Messages = Standard
+ Pool = Default
+ Where = @tmpdir@/bacula-restores
+ Max Run Time = 30min
+}
+
+
+# List of files to be backed up
+FileSet {
+ Name = "Full Set"
+ Include {
+ Options { signature=MD5; sparse=yes;
+ verify=pins5 }
+ File = <@tmpdir@/file-list
+ }
+}
+
+# List of files to be backed up
+FileSet {
+ Name = "FSTypeFS"
+ Include {
+ Options {
+ signature=MD5
+ fstype = ext4
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+
+# List of files to be backed up
+FileSet {
+ Name = "FSTypeFSEmpty"
+ Include {
+ Options {
+ signature=MD5
+ fstype = debugfs
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "FSno5"
+ Include { Options { verify=s5 }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { compression=GZIP }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { compression=LZO }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { compression=LZO; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { compression=GZIP; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "FS5"
+ Include { Options { signature=MD5; verify=s5 }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; compression=GZIP }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; compression=LZO }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; compression=LZO; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; compression=GZIP; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "FS5nocheck"
+ Include { Options { verify="nog"; signature=MD5 }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; compression=GZIP }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; compression=LZO }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; compression=LZO; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=MD5; compression=GZIP; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "FS1"
+ Include { Options { verify=s1; signature=SHA1 }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=SHA1; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=SHA1; compression=GZIP }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=SHA1; compression=LZO }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=SHA1; compression=LZO; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+ Include { Options { signature=SHA1; compression=GZIP; sparse=yes }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "SimpleSet"
+ Include {
+ Options { signature=MD5 }
+ File = <@tmpdir@/file-list
+ }
+}
+
+
+FileSet {
+ Name = vSphereSet
+ Include {
+ Options {
+ signature=MD5
+ compression = GZIP1
+ }
+ Plugin = "vsphere: host=@VSPHERE_HOST1@"
+ }
+}
+
+FileSet {
+ Name = vSphereSet2
+ Include {
+ Options {
+ signature=MD5
+ compression = GZIP1
+ }
+ Plugin = "vsphere: host=@VSPHERE_HOST1@"
+ Plugin = "vsphere: host=@VSPHERE_HOST2@"
+ }
+}
+
+FileSet {
+ Name = "SparseSet"
+ Include {
+ Options {
+ signature=MD5
+ sparse=yes
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "CompressedSet"
+ Include {
+ Options {
+ signature=MD5
+ compression=GZIP
+ onefs=no
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "HardlinkSet"
+ Include {
+ Options {
+ signature=MD5
+ hardlinks=yes
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "LZOSet"
+ Include {
+ Options {
+ signature=MD5
+ compression=LZO
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "DeltaSet"
+ Include {
+ Options {
+ signature=MD5
+ plugin = delta
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "FIFOSet"
+ Include {
+ Options {
+ readfifo = yes
+ signature=MD5
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+
+FileSet {
+ Name = "SparseCompressedSet"
+ Include {
+ Options {
+ signature=MD5
+ compression=GZIP
+ sparse=yes
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "SparseLZOSet"
+ Include {
+ Options {
+ signature=MD5
+ compression=LZO
+ sparse=yes
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+FileSet {
+ Name = "MonsterFileSet"
+ Include {
+ Options {
+ signature = MD5
+ noatime = yes
+ ignore case = yes
+ Exclude = yes
+ RegexDir = "Cache"
+ RegexDir = "Windows Defender"
+ RegexDir = "Temporary Internet Files"
+ RegexDir = "bacula"
+ RegexDir = "Temp"
+
+ RegexDir = "ATI Technologies"
+
+ RegexDir = "wmdownloads"
+ RegexDir = "My Music"
+ RegexDir = "iTunes"
+ RegexDir = "Cookies"
+
+ RegexFile = "desktop.ini"
+ RegexFile = "thumbs.db"
+ RegexFile = "acrobat7.exe"
+ RegexFile = "acr6win.exe"
+ RegexFile = "AdbeRdr70_enu_full.exe"
+ RegexFile = "antivirus10_1_5.exe"
+ #thunderbird lock file
+ RegexFile = "parent.lock"
+
+ RegexDir = "Retrospect Restore Points"
+
+ #exclude i386 director of windows installer files
+ WildDir = "[A-Z]:/i386"
+
+ # Exclude Mozilla-based programs' file caches
+ WildDir = "[A-Z]:/Documents and Settings/*/Application Data/*/Profiles/*/*/ImapMail"
+ WildDir = "[A-Z]:/Users/*/Application Data/*/Profiles/*/*/ImapMail"
+
+ # Exclude user's registry files - they're always in use anyway.
+ WildFile = "[A-Z]:/Documents and Settings/*/Local Settings/Application Data/Microsoft/Windows/usrclass.*"
+ WildFile = "[A-Z]:/Users/*/Local Settings/Application Data/Microsoft/Windows/usrclass.*"
+ WildFile = "[A-Z]:/Documents and Settings/*/ntuser.*"
+ WildFile = "[A-Z]:/Users/*/ntuser.*"
+
+ WildDir = "[A-Z]:/Documents and Settings/*/Recent"
+ WildDir = "[A-Z]:/Users/*/Recent"
+
+ WildDir = "[A-Z]:/Documents and Settings/*/Local Settings/History"
+ WildDir = "[A-Z]:/Users/*/Local Settings/History"
+
+ # These are always open and unable to be backed up
+ WildFile = "[A-Z]:/Documents and Settings/All Users/Application Data/Microsoft/Network/Downloader/qmgr[01].dat"
+ WildFile = "[A-Z]:/Users/All Users/Application Data/Microsoft/Network/Downloader/qmgr[01].dat"
+
+ #Exclude all of Windows...
+ WildDir = "[A-Z]:/windows"
+ WildDir = "[A-Z]:/winnt"
+ WildDir = "[A-Z]:/winxp"
+ WildDir = "[A-Z]:/win"
+
+ #symantec antivirus app stuff
+ WildDir = "[A-Z]:/*/Symantec*"
+
+ #system volume information
+ WildDir = "[A-Z]:/System Volume Information"
+
+ WildFile = "*.tmp"
+ # ghost image and spanning files
+ WildFile = "*.gho"
+ WildFile = "*.ghs"
+
+ # Recycle bins
+ WildDir = "[A-Z]:/RECYCLER"
+ WildDir = "[A-Z]:/RECYCLER"
+ WildDir = "[A-Z]:/RECYCLED"
+ WildDir = "[A-Z]:/$RECYCLE.BIN"
+
+ # Swap files
+ WildFile = "[A-Z]:/pagefile.sys"
+
+ # These are programs and are easier to reinstall than restore from
+ # backup
+ WildDir = "[A-Z]:/cygwin"
+ WildDir = "[A-Z]:/Program Files/Adobe/Acrobat 7.0"
+ WildDir = "[A-Z]:/Program Files/Adobe/Acrobat 8.0"
+
+ WildDir = "[A-Z]:/Program Files/Common Files/Software Center"
+ WildDir = "[A-Z]:/Software Center"
+
+ WildDir = "[A-Z]:/Program Files/Grisoft"
+ WildDir = "[A-Z]:/Program Files/Java"
+ WildDir = "[A-Z]:/Program Files/Java Web Start"
+ WildDir = "[A-Z]:/Program Files/JavaSoft"
+ WildDir = "[A-Z]:/Program Files/Microsoft Office"
+ WildDir = "[A-Z]:/Program Files/Mozilla Firefox"
+ WildDir = "[A-Z]:/Program Files/Mozilla Thunderbird"
+ WildDir = "[A-Z]:/Program Files/mozilla.org"
+ WildDir = "[A-Z]:/Program Files/OpenOffice*"
+ }
+ File = <@tmpdir@/file-list
+ }
+}
+
+
+#
+# When to do the backups, full backup on first sunday of the month,
+# differential (i.e. incremental since full) every other sunday,
+# and incremental backups other days
+Schedule {
+ Name = "WeeklyCycle"
+ Run = Level=Full 1st sun at 1:05
+ Run = Level=Differential 2nd-5th sun at 1:05
+ Run = Level=Incremental mon-sat at 1:05
+}
+
+# This schedule does the catalog. It starts after the WeeklyCycle
+Schedule {
+ Name = "WeeklyCycleAfterBackup"
+ Run = Level=Full sun-sat at 1:10
+}
+
+# This is the backup of the catalog
+FileSet {
+ Name = "Catalog"
+ Include {
+ Options {
+ signature=MD5
+ }
+ File = /home/kern/bacula/regress/bin/working/bacula.sql
+ }
+}
+
+# Client (File Services) to backup
+Client {
+ Name = @hostname@-fd
+ Address = @hostname@
+ FDPort = @fdport@
+ Catalog = MyCatalog
+ Password = "xevrjURYoCHhn26RaJoWbeWXEY/a3VqGKp/37tgWiuHc" # password for FileDaemon
+ File Retention = 30d # 30 days
+ Job Retention = 180d # six months
+ AutoPrune = yes # Prune expired Jobs/Files
+ Maximum Concurrent Jobs = 10
+}
+
+# Definiton of file storage device
+Storage {
+ Name = File
+ Address = @hostname@ # N.B. Use a fully qualified name here
+ SDPort = @sdport@
+ Password = "ccV3lVTsQRsdIUGyab0N4sMDavui2hOBkmpBU0aQKOr9"
+ Device = FileStorage
+ Media Type = File
+ Maximum Concurrent Jobs = 10
+ Heartbeat Interval = 330
+}
+
+Storage {
+ Name = File1
+ Address = @hostname@ # N.B. Use a fully qualified name here
+ SDPort = @sdport@
+ Password = "ccV3lVTsQRsdIUGyab0N4sMDavui2hOBkmpBU0aQKOr9"
+ Device = FileStorage1
+ Media Type = File1
+ Maximum Concurrent Jobs = 10
+ Heartbeat Interval = 330
+}
+
+Storage {
+ Name = File2
+ Address = @hostname@ # N.B. Use a fully qualified name here
+ SDPort = @sdport@
+ Password = "ccV3lVTsQRsdIUGyab0N4sMDavui2hOBkmpBU0aQKOr9"
+ Device = FileStorage2
+ Media Type = File
+ Maximum Concurrent Jobs = 10
+ Heartbeat Interval = 330
+}
+
+# Definition of DLT tape storage device
+#Storage {
+# Name = DLTDrive
+# Address = @hostname@ # N.B. Use a fully qualified name here
+# SDPort = @sdport@
+# Password = "ccV3lVTsQRsdIUGyab0N4sMDavui2hOBkmpBU0aQKOr9" # password for Storage daemon
+# Device = "HP DLT 80" # must be same as Device in Storage daemon
+# Media Type = DLT8000 # must be same as MediaType in Storage daemon
+#}
+
+# Definition of DDS tape storage device
+#Storage {
+# Name = SDT-10000
+# Address = @hostname@ # N.B. Use a fully qualified name here
+# SDPort = @sdport@
+# Password = "ccV3lVTsQRsdIUGyab0N4sMDavui2hOBkmpBU0aQKOr9" # password for Storage daemon
+# Device = SDT-10000 # must be same as Device in Storage daemon
+# Media Type = tape # must be same as MediaType in Storage daemon
+#}
+
+# Definition of 8mm tape storage device
+#Storage {
+# Name = "8mmDrive"
+# Address = @hostname@ # N.B. Use a fully qualified name here
+# SDPort = @sdport@
+# Password = "ccV3lVTsQRsdIUGyab0N4sMDavui2hOBkmpBU0aQKOr9"
+# Device = "Exabyte 8mm"
+# MediaType = "8mm"
+#}
+
+
+# Generic catalog service
+Catalog {
+ Name = MyCatalog
+ @libdbi@
+ dbname = @db_name@; user = @db_user@; password = "@db_password@"
+}
+
+# Reasonable message delivery -- send most everything to email address
+# and to the console
+Messages {
+ Name = Standard
+ mailcommand = "@sbindir@/bsmtp -h localhost -f \"\(Bacula regression\) %r\" -s \"Regression: %t %e of %c %l\" %r"
+ operatorcommand = "@sbindir@/bsmtp -h localhost -f \"\(Bacula regression\) %r\" -s \"Regression: Intervention needed for %j\" %r"
+# MailOnError = @job_email@ = all
+# operator = @job_email@ = mount
+ console = all, !skipped, !terminate, !restored
+#
+# WARNING! the following will create a file that you must cycle from
+# time to time as it will grow indefinitely. However, it will
+# also keep all your messages if the scroll off the console.
+#
+ append = "@working_dir@/log" = all, !skipped
+ catalog = all, !skipped
+}
+
+Messages {
+ Name = NoEmail
+ mailcommand = "@sbindir@/bsmtp -h localhost -f \"\(Bacula regression\) %r\" -s \"Regression: %t %e of %c %l\" %r"
+ console = all, !skipped, !terminate
+#
+# WARNING! the following will create a file that you must cycle from
+# time to time as it will grow indefinitely. However, it will
+# also keep all your messages if the scroll off the console.
+#
+ append = "@working_dir@/log" = all, !skipped
+ catalog = all, !skipped
+}
+
+
+# Default pool definition
+Pool {
+ Name = Default
+ Pool Type = Backup
+ Recycle = yes # Bacula can automatically recycle Volumes
+ AutoPrune = yes # Prune expired volumes
+ Volume Retention = 365d # one year
+# Label Format = "TEST-${Year}-${Month:p/2/0/r}-${Day:p/2/0/r}:${NumVols}"
+# Simple Label Format = "Backup-"
+# Maximum Volume Jobs = 1
+# Maximum Volume Bytes = 1500000
+}
+
+Statistics {
+ Name = CSV1
+ Interval = 1 minute
+ File = "@tmpdir@/stats-dir.csv"
+ Type = csv
+}
+
+Statistics {
+ Name = CSV2
+ Interval = 1 minute
+ File = "@tmpdir@/stats-dir-memory.csv"
+ Type = csv
+ Prefix = "backup"
+ Metrics = "bacula.dir.memory.*"
+ Metrics = "!bacula.dir.memory.bufs"
+}
+
+Statistics {
+ Name = Graphite1
+ Interval = 1 minute
+ Type = graphite
+ Prefix = "backup"
+ Host = localhost
+ Port = 9223
+ Metrics = "bacula.dir.config.jobs"
+}
+
--- /dev/null
+#
+# Default Bacula File Daemon Configuration file
+#
+# For Bacula release 1.33
+#
+# There is not much to change here except perhaps the
+# File daemon Name to
+#
+
+#
+# List Directors who are permitted to contact this File daemon
+#
+Director {
+ Name = @hostname@-dir
+ Password = "xevrjURYoCHhn26RaJoWbeWXEY/a3VqGKp/37tgWiuHc"
+}
+
+#
+# "Global" File daemon configuration specifications
+#
+FileDaemon { # this is me
+ Name = @hostname@-fd
+ FDPort = @fdport@ # where we listen for the director
+ WorkingDirectory = "@working_dir@"
+ Pid Directory = "@piddir@"
+ SubSys Directory = "@subsysdir@"
+ Plugin Directory = "@sbindir@/plugins"
+ Maximum Concurrent Jobs = 100
+}
+
+# Send all messages except skipped files back to Director
+Messages {
+ Name = Standard
+ director = @hostname@-dir = all, !terminate, !restored
+}
+
+Statistics {
+ Name = CSV1
+ Interval = 1 minute
+ File = "@tmpdir@/stats-fd.csv"
+ Type = csv
+}
+
+Statistics {
+ Name = CSV2
+ Interval = 1 minute
+ File = "@tmpdir@/stats-fd-memory.csv"
+ Type = csv
+ Prefix = "backup"
+ Metrics = "bacula.client.@hostname@-fd.memory.*"
+ Metrics = "!bacula.client.@hostname@-fd.memory.bufs"
+}
+
+Statistics {
+ Name = Graphite1
+ Interval = 1 minute
+ Type = graphite
+ Prefix = "backup"
+ Host = localhost
+ Port = 9223
+ Metrics = "bacula.client.@hostname@-fd.memory.bufs"
+}
+
--- /dev/null
+#
+# Default Bacula Storage Daemon Configuration file
+#
+# For Bacula release 1.33
+#
+# You may need to change the name of your tape drive
+# on the "Archive Device" directive in the Device
+# resource. If you change the Name and/or the
+# "Media Type" in the Device resource, please ensure
+# that dird.conf has corresponding changes.
+#
+
+Storage { # definition of myself
+ Name = @hostname@-sd
+ SDPort = @sdport@ # Director's port
+ WorkingDirectory = "@working_dir@"
+ Pid Directory = "@piddir@"
+ Subsys Directory = "@subsysdir@"
+ Plugin Directory = "@sbindir@/plugins"
+ Maximum Concurrent Jobs = 100
+}
+
+#
+# List Directors who are permitted to contact Storage daemon
+#
+Director {
+ Name = @hostname@-dir
+ Password = "ccV3lVTsQRsdIUGyab0N4sMDavui2hOBkmpBU0aQKOr9"
+}
+
+#
+# Devices supported by this Storage daemon
+# To connect, the Director's bacula-dir.conf must have the
+# same Name and MediaType.
+#
+
+Device {
+ Name = FileStorage
+ Media Type = File
+ Archive Device = @tmpdir@
+ LabelMedia = yes; # lets Bacula label unlabelled media
+ Random Access = Yes;
+ AutomaticMount = yes; # when device opened, read it
+ RemovableMedia = no;
+ AlwaysOpen = no;
+# Maximum File Size = 10KB
+}
+
+Device {
+ Name = FileStorage1
+ Media Type = File1
+ Archive Device = @tmpdir@
+ LabelMedia = yes; # lets Bacula label unlabelled media
+ Random Access = Yes;
+ AutomaticMount = yes; # when device opened, read it
+ RemovableMedia = no;
+ AlwaysOpen = no;
+}
+
+Device {
+ Name = Dummy
+ Media Type = Dummy
+ Device Type = Fifo
+ Archive Device = /dev/null
+ LabelMedia = yes
+ Random Access = no
+ AutomaticMount = no
+ RemovableMedia = no
+ MaximumOpenWait = 60
+ AlwaysOpen = no
+}
+
+#Device {
+# Name = "HP DLT 80"
+# Media Type = DLT8000
+# Archive Device = /dev/nst0
+# AutomaticMount = yes; # when device opened, read it
+# AlwaysOpen = yes;
+# RemovableMedia = yes;
+#}
+
+#Device {
+# Name = SDT-7000 #
+# Media Type = DDS-2
+# Archive Device = /dev/nst0
+# AutomaticMount = yes; # when device opened, read it
+# AlwaysOpen = yes;
+# RemovableMedia = yes;
+#}
+
+#Device {
+# Name = Floppy
+# Media Type = Floppy
+# Archive Device = /mnt/floppy
+# RemovableMedia = yes;
+# Random Access = Yes;
+# AutomaticMount = yes; # when device opened, read it
+# AlwaysOpen = no;
+#}
+
+#
+# A very old Exabyte with no end of media detection
+#
+#Device {
+# Name = "Exabyte 8mm"
+# Media Type = "8mm"
+# Archive Device = /dev/nst0
+# Hardware end of medium = No;
+# AutomaticMount = yes; # when device opened, read it
+# AlwaysOpen = Yes;
+# RemovableMedia = yes;
+#}
+
+#
+# Send all messages to the Director,
+# mount messages also are sent to the email address
+#
+Messages {
+ Name = Standard
+ director = @hostname@-dir = all, !terminate
+}
+
+Cloud {
+ Name = DummyCloud
+ Driver = "File"
+ HostName = "@tmpdir@/cloud"
+ BucketName = "DummyBucket"
+ AccessKey = "DummyAccessKey"
+ SecretKey = "DummySecretKey"
+ Protocol = HTTPS
+ UriStyle = VirtualHost
+}
+
+Statistics {
+ Name = CSV1
+ Interval = 1 minute
+ File = "@tmpdir@/stats-sd.csv"
+ Type = csv
+}
+
+Statistics {
+ Name = CSV2
+ Interval = 1 minute
+ File = "@tmpdir@/stats-sd-memory.csv"
+ Type = csv
+ Prefix = "backup"
+ Metrics = "bacula.storage.@hostname@-sd.memory.*"
+ Metrics = "!bacula.storage.@hostname@-sd.memory.bufs"
+}
+
+Statistics {
+ Name = Graphite1
+ Interval = 1 minute
+ Type = graphite
+ Prefix = "backup"
+ Host = localhost
+ Port = 9223
+ Metrics = "bacula.storage.@hostname@-sd.config.devices"
+}
+
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2000-2015 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+# Radosław Korzeniewski, MMXVIII
+# radoslaw@korzeniewski.net, radekk@inteos.pl
+# Inteos Sp. z o.o. http://www.inteos.pl/
+#
+
+cp -f ${rscripts}/collect-bacula-dir.conf ${conf}/bacula-dir.conf
+cp -f ${rscripts}/collect-bacula-sd.conf ${conf}/bacula-sd.conf
+cp -f ${rscripts}/collect-bacula-fd.conf ${conf}/bacula-fd.conf
+cp -f ${rscripts}/test-console.conf ${conf}/bconsole.conf
+cp -f ${rscripts}/test-console.conf ${conf}/bat.conf
+
sed -f ${out} ${rscripts}/tls-bacula-sd.conf.in >${rscripts}/tls-bacula-sd.conf
sed -f ${out} ${rscripts}/tls-bacula-dir.conf.in >${rscripts}/tls-bacula-dir.conf
+sed -f ${out} ${rscripts}/collect-bacula-dir.conf.in >${rscripts}/collect-bacula-dir.conf
+sed -f ${out} ${rscripts}/collect-bacula-sd.conf.in >${rscripts}/collect-bacula-sd.conf
+sed -f ${out} ${rscripts}/collect-bacula-fd.conf.in >${rscripts}/collect-bacula-fd.conf
+
sed -f ${out} ${rscripts}/tls-auth-bacula-fd.conf.in >${rscripts}/tls-auth-bacula-fd.conf
sed -f ${out} ${rscripts}/tls-auth-bacula-sd.conf.in >${rscripts}/tls-auth-bacula-sd.conf
sed -f ${out} ${rscripts}/tls-auth-bacula-dir.conf.in >${rscripts}/tls-auth-bacula-dir.conf
-
sed -f ${out} ${rscripts}/bacula-fd-2d.conf.in >${rscripts}/bacula-fd-2d.conf
sed -f ${out} ${rscripts}/bacula-sd-2d.conf.in >${rscripts}/bacula-sd-2d.conf
sed -f ${out} ${rscripts}/bacula-dir-2d.conf.in >${rscripts}/bacula-dir-2d.conf
update_client $HOST $BASEPORT add_to_backup_list
run_bconsole run_bacula start_test end_test create_bconcmds
create_many_dirs cleanup start_bacula
- get_dirname check_jobmedia_content
+ get_dirname check_jobmedia_content setup_collector
stop_bacula get_resource set_maximum_concurrent_jobs get_time
add_attribute check_prune_list check_min_volume_size
init_delta update_delta check_max_backup_size comment_out
}
}
+sub setup_collector
+{
+ my ($conf, $name, $type, $interval) = @_;
+ my $file='';
+
+ $name = $name || "collector1";
+ $type = $type || "csv";
+ $interval = $interval || 60;
+
+ if ($type eq 'csv') {
+ $file = "File = \"$tmp/$name.csv\"";
+ } else {
+ $file = "Host = localhost\nPort = 9223\n";
+ }
+ open(FP, ">>$conf") or die "Error: Unable to open $conf $@";
+ print FP "
+Statistics {
+ Name = $name
+ Interval = 60
+ Type = $type
+ $file
+}
+";
+ close(FP);
+}
+
use Fcntl 'SEEK_SET';
use Data::Dumper;
sub check_aligned_data
}
1;
+
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2000-2015 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+# Radosław Korzeniewski, MMXVIII
+# radoslaw@korzeniewski.net, radekk@inteos.pl
+# Inteos Sp. z o.o. http://www.inteos.pl/
+#
+# This is an ConfigFile/ini unit test
+#
+TestName="collect_test"
+. scripts/functions
+make -C $src/src/lib $TestName
+
+$src/src/lib/$TestName
+exit $?
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2000-2015 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+# Radosław Korzeniewski, MMXVIII
+# radoslaw@korzeniewski.net, radekk@inteos.pl
+# Inteos Sp. z o.o. http://www.inteos.pl/
+#
+# The test verifies if the internal collector is runing and changing statistic metric values
+#
+TestName="statistics-backend-test"
+JobName=Simple
+. scripts/functions
+
+cwd=`pwd`
+scripts/cleanup
+scripts/copy-collect-confs
+echo "${cwd}/build" >${cwd}/tmp/file-list
+#export debug=1
+
+start_test
+
+cat <<END_OF_DATA >tmp/bconcmds
+@output /dev/null
+messages
+@# wait a little bit for backend to start
+@sleep 10
+@# disaplay a backends statuses
+@$out tmp/log1.out
+.api 2
+.status dir statistics
+@$out tmp/log2.out
+.api 2
+.status storage statistics
+@$out tmp/log3.out
+.api 2
+.status client statistics
+quit
+END_OF_DATA
+
+run_bacula
+# wait for Graphite backend to start spooling
+sleep 120
+
+cat <<END_OF_DATA >tmp/bconcmds
+@$out tmp/log4.out
+.api 2
+.status dir statistics
+@$out tmp/log5.out
+.api 2
+.status storage statistics
+@$out tmp/log6.out
+.api 2
+.status client statistics
+quit
+END_OF_DATA
+
+run_bconsole
+# start a Graphite simulator :)
+netcat -l -p 9223 -w 180 > ${cwd}/tmp/log7.out
+netcat -l -p 9223 -w 180 > ${cwd}/tmp/log8.out
+netcat -l -p 9223 -w 180 > ${cwd}/tmp/log9.out
+
+#cat <<END_OF_DATA >tmp/bconcmds
+#@# FD metrics
+#collect client all
+#@$out tmp/log3.out
+#@# SD metrics
+#collect storage all
+## now run some jobs
+#@$out tmp/log4.out
+#label storage=File1 volume=TestVolume001
+#run job=$JobName storage=File1 yes
+#wait
+#messages
+#@$out tmp/log5.out
+#@# Director metrics
+#collect all
+#@$out tmp/log6.out
+#@# FD metrics
+#collect client all
+#@$out tmp/log7.out
+#@# SD metrics
+#collect storage all
+#quit
+#END_OF_DATA
+
+check_for_zombie_jobs storage=File1
+stop_bacula
+
+vstat=0
+
+# check if all collectors are running
+dcr=`grep running ${cwd}/tmp/log1.out | wc -l`
+scr=`grep running ${cwd}/tmp/log2.out | wc -l`
+fcr=`grep running ${cwd}/tmp/log3.out | wc -l`
+if [ $dcr -ne 4 -o $scr -ne 4 -o $fcr -ne 4 ]
+then
+ echo "Problem with collectors running!"
+ vstat=$((vstat+1))
+fi
+
+# check if Graphite collector are spooling
+dcs=`grep "spooling=in progress" ${cwd}/tmp/log4.out | wc -l`
+scs=`grep "spooling=in progress" ${cwd}/tmp/log5.out | wc -l`
+fcs=`grep "spooling=in progress" ${cwd}/tmp/log6.out | wc -l`
+if [ $dcs -ne 1 -o $scs -ne 1 -o $fcs -ne 1 ]
+then
+ echo "Problem with collectors spooling!"
+ vstat=$((vstat+1))
+fi
+
+# check what collectors has written to stat files
+timecoll=`head -1 ${cwd}/tmp/stats-dir.csv | cut -f1 -d','`
+dircollnr=`grep $timecoll ${cwd}/tmp/stats-dir.csv | wc -l`
+timecoll=`head -1 ${cwd}/tmp/stats-dir-memory.csv | cut -f1 -d','`
+dirmemcollnr=`grep $timecoll ${cwd}/tmp/stats-dir-memory.csv | wc -l`
+if [ $dircollnr -ne 25 -o $dirmemcollnr -ne 4 ]
+then
+ echo "Problem with CSV Dir collector!"
+ vstat=$((vstat+1))
+fi
+
+timecoll=`head -1 ${cwd}/tmp/stats-sd.csv | cut -f1 -d','`
+sdcollnr=`grep $timecoll ${cwd}/tmp/stats-sd.csv | wc -l`
+timecoll=`head -1 ${cwd}/tmp/stats-sd-memory.csv | cut -f1 -d','`
+sdmemcollnr=`grep $timecoll ${cwd}/tmp/stats-sd-memory.csv | wc -l`
+if [ $sdcollnr -ne 34 -o $dirmemcollnr -ne 4 ]
+then
+ echo "Problem with CSV SD collector!"
+ vstat=$((vstat+1))
+fi
+
+timecoll=`head -1 ${cwd}/tmp/stats-fd.csv | cut -f1 -d','`
+fdcollnr=`grep $timecoll ${cwd}/tmp/stats-fd.csv | wc -l`
+timecoll=`head -1 ${cwd}/tmp/stats-fd-memory.csv | cut -f1 -d','`
+fdmemcollnr=`grep $timecoll ${cwd}/tmp/stats-fd-memory.csv | wc -l`
+if [ $fdcollnr -ne 5 -o $fdmemcollnr -ne 4 ]
+then
+ echo "Problem with CSV FD collector!"
+ vstat=$((vstat+1))
+fi
+
+gdrm=`grep "backup.bacula.dir.config.jobs 20" ${cwd}/tmp/log7.out ${cwd}/tmp/log8.out ${cwd}/tmp/log9.out | wc -l`
+gsrm=`grep "backup.bacula.storage.*.config.devices 3" ${cwd}/tmp/log7.out ${cwd}/tmp/log8.out ${cwd}/tmp/log9.out | wc -l`
+gfrm=`grep "backup.bacula.client.*.memory.bufs " ${cwd}/tmp/log7.out ${cwd}/tmp/log8.out ${cwd}/tmp/log9.out | wc -l`
+if [ $gdrm -le 1 -o $gsrm -le 1 -o $gfrm -le 1 ]
+then
+ echo "Problem with collectors despooling!"
+ vstat=$((vstat+1))
+fi
+
+end_test
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2000-2015 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+# Radosław Korzeniewski, MMXVIII
+# radoslaw@korzeniewski.net, radekk@inteos.pl
+# Inteos Sp. z o.o. http://www.inteos.pl/
+#
+# The test verifies if the internal collector is runing and changing statistic metric values
+#
+TestName="statistics-cmd-test"
+JobName=Simple
+. scripts/functions
+
+cwd=`pwd`
+scripts/cleanup
+scripts/copy-test-confs
+echo "${cwd}/build" >${cwd}/tmp/file-list
+
+start_test
+
+cat <<END_OF_DATA >tmp/bconcmds
+@output /dev/null
+messages
+@$out tmp/log1.out
+@# Director metrics
+statistics all
+@$out tmp/log2.out
+@# FD metrics
+statistics client all
+@$out tmp/log3.out
+@# SD metrics
+statistics storage all
+# now run some jobs
+@$out tmp/log4.out
+label storage=File1 volume=TestVolume001
+run job=$JobName storage=File1 yes
+wait
+messages
+@$out tmp/log5.out
+@# Director metrics
+statistics all
+@$out tmp/log6.out
+@# FD metrics
+statistics client all
+@$out tmp/log7.out
+@# SD metrics
+statistics storage all
+@$out tmp/log8.out
+@# show command
+show statistics
+quit
+END_OF_DATA
+
+run_bacula
+check_for_zombie_jobs storage=File1
+stop_bacula
+
+vstat=0
+
+# now chack if collect command was working
+t1=`grep bacula.jobs.all ${cwd}/tmp/log1.out | wc -l`
+t2=`grep bacula.*.heap ${cwd}/tmp/log2.out | wc -l`
+t3=`grep bacula.*.heap ${cwd}/tmp/log3.out | wc -l`
+if [ $t1 -ne 1 -o $t2 -ne 1 -o $t3 -ne 1 ]
+then
+ vstat=$((vstat+1))
+ echo "Collect command is not working!"
+fi
+d1=`diff ${cwd}/tmp/log1.out ${cwd}/tmp/log5.out | wc -l`
+d2=`diff ${cwd}/tmp/log2.out ${cwd}/tmp/log6.out | wc -l`
+d3=`diff ${cwd}/tmp/log3.out ${cwd}/tmp/log7.out | wc -l`
+if [ $d1 -eq 0 -o $d2 -eq 0 -o $d3 -eq 0 ]
+then
+ vstat=$((vstat+1))
+ echo "Internal collector is not working!"
+fi
+if [ $t1 -eq 1 ]
+then
+ j1=`grep bacula.jobs.all ${cwd}/tmp/log5.out | cut -f2 -d'='`
+ if [ $j1 -ne 1 ]
+ then
+ vstat=$((vstat+1))
+ echo "Internal collector not updating correctly!"
+ fi
+fi
+sd=`grep "^Statistics" $conf/bacula-dir.conf | wc -l`
+sc=`grep "^Statistics:" ${cwd}/tmp/log8.out | wc -l`
+if [ $sd -ne $sc ]
+then
+ vstat=$((vstat+1))
+ echo "show command is not working properly!"
+fi
+
+end_test
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2000-2018 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+# Radosław Korzeniewski, MMXVIII
+# radoslaw@korzeniewski.net, radekk@inteos.pl
+# Inteos Sp. z o.o. http://www.inteos.pl/
+#
+# The test verifies if the reload process after adding a collector resource
+#
+TestName="statistics-reload-test"
+JobName=Simple
+. scripts/functions
+
+cwd=`pwd`
+scripts/cleanup
+scripts/copy-test-confs
+echo "${cwd}/build" >${cwd}/tmp/file-list
+
+$bperl -e 'setup_collector("$tmp/bacula-dir.conf.new", "MyCollector")'
+echo "@$tmp/bacula-dir.conf.new" >> $conf/bacula-dir.conf
+
+start_test
+
+cat <<END_OF_DATA >tmp/bconcmds
+@output /dev/null
+messages
+@$out tmp/log1.out
+show statistics
+quit
+END_OF_DATA
+
+run_bacula
+
+# Clear the collector
+> $tmp/bacula-dir.conf.new
+
+cat <<END_OF_DATA >tmp/bconcmds
+@$out tmp/log2.out
+reload
+show statistics
+quit
+END_OF_DATA
+
+run_bconsole
+
+$bperl -e 'setup_collector("$tmp/bacula-dir.conf.new", "MyCollector2")'
+
+cat <<END_OF_DATA >tmp/bconcmds
+@$out tmp/log3.out
+reload
+show statistics
+quit
+END_OF_DATA
+
+run_bconsole
+
+grep "MyCollector" $tmp/log1.out > /dev/null
+if [ $? != 0 ]; then
+ print_debug "ERROR: Should find MyCollector in $tmp/log1.out"
+ estat=1
+fi
+
+grep "MyCollector" $tmp/log2.out > /dev/null
+if [ $? -eq 0 ]; then
+ print_debug "ERROR: Should not find MyCollector in $tmp/log2.out"
+ estat=1
+fi
+
+grep "MyCollector" $tmp/log3.out > /dev/null
+if [ $? != 0 ]; then
+ print_debug "ERROR: Should find MyCollector in $tmp/log3.out"
+ estat=1
+fi
+
+stop_bacula
+end_test