1 /*******************************************************************************
2 
3     Application extension to parse configuration for the stats output.
4 
5     This extension writes to a file the list of value and identifier in
6     an easily parseable way. It also has the option to send metrics
7     directly to a Collectd unix socket.
8 
9     Should one want to use the Collectd socket, several additional configuration
10     values need to be provided:
11     - path to the unix socket (which enables the option);
12     - application name: If not provided, default to the name passed to the
13         application framework;
14     - application instance: Optional, no default value
15     - hostname: If not provided, the value of `gethostname` (2) will be used;
16     - default type: A convention on the type of the 'application stats',
17         which will be used when calling `StatsLog.add`.
18         Defaults to `application_name ~ "_stats"`.
19 
20     Copyright:
21         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
22         All rights reserved.
23 
24     License:
25         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
26         Alternatively, this file may be distributed under the terms of the Tango
27         3-Clause BSD License (see LICENSE_BSD.txt for details).
28 
29 *******************************************************************************/
30 
31 module ocean.util.app.ext.StatsExt;
32 
33 
34 
35 
36 import core.sys.posix.unistd : gethostname;
37 
38 import ocean.core.Enforce;
39 import ocean.core.TypeConvert;
40 import ocean.sys.ErrnoException;
41 
42 import ocean.util.app.model.ExtensibleClassMixin;
43 import ocean.util.app.Application;
44 import ocean.util.app.ext.model.IConfigExtExtension;
45 import ocean.util.app.ext.model.ILogExtExtension;
46 import ocean.util.app.ext.ConfigExt;
47 
48 import ocean.util.app.ext.ReopenableFilesExt;
49 
50 import ocean.util.config.ConfigParser;
51 import ocean.util.log.Appender;
52 import ocean.util.log.Stats;
53 import ConfigFiller = ocean.util.config.ConfigFiller;
54 
55 import ocean.transition;
56 import ocean.core.Verify;
57 import ocean.io.device.File;
58 
59 
60 /*******************************************************************************
61 
62     Application extension to parse configuration files for the stats output.
63 
64 *******************************************************************************/
65 
66 class StatsExt : IConfigExtExtension
67 {
68     /***************************************************************************
69 
70         Stats Log instance
71 
72     ***************************************************************************/
73 
74     public StatsLog stats_log;
75 
76 
77     /// Config instance for creating the default StatsLog.
78     public StatsLog.Config config;
79 
80 
81     /***************************************************************************
82 
83         Extension order. This extension uses -500 because it should be
84         called early, but after the LogExt extension.
85 
86         Returns:
87             the extension order
88 
89     ***************************************************************************/
90 
91     public override int order ( )
92     {
93         return -500;
94     }
95 
96 
97     /***************************************************************************
98 
99         Parse the configuration file options to set up the stats log.
100 
101         Params:
102             app = the application instance
103             parser = configuration instance
104 
105     ***************************************************************************/
106 
107     public override void processConfig ( IApplication app, ConfigParser parser )
108     {
109         ConfigFiller.fill!(StatsLog.Config)("STATS", this.config, parser);
110 
111         if (!this.config.app_name.length)
112             this.config.app_name = app.name;
113         if (!this.config.hostname.length)
114             this.config.hostname = getHostName();
115         if (!this.config.default_type.length)
116             this.config.default_type = this.config.app_name ~ "_stats";
117 
118         verify(this.config.app_name.length != 0);
119 
120         this.stats_log = this.newStatsLog(app, this.config);
121     }
122 
123 
124     /***************************************************************************
125 
126         Creates a new stats log instance according to the provided config
127         settings. If the reopenable files extension exists, the log file is
128         registered with it.
129 
130         Params:
131             app = the application instance
132             stats_config = stats log configuration instance
133 
134         Returns:
135             new, configured StatsLog instance
136 
137     ***************************************************************************/
138 
139     static public StatsLog newStatsLog ( IApplication app,
140         StatsLog.Config stats_config )
141     {
142         Appender newAppender ( istring file, Appender.Layout layout )
143         {
144             auto stream = new File(file, File.WriteAppending);
145 
146             if ( auto reopenable_files_ext =
147                 (cast(Application)app).getExtension!(ReopenableFilesExt) )
148             {
149                 reopenable_files_ext.register(stream);
150             }
151 
152             return new AppendStream(stream, true, layout);
153         }
154 
155         return new StatsLog(stats_config, &newAppender, stats_config.file_name);
156     }
157 
158     /***************************************************************************
159 
160         Unused IConfigExtExtension method.
161 
162         We just need to provide an "empty" implementation to satisfy the
163         interface.
164 
165         Params:
166             app = the application instance
167             config = configuration instance
168 
169     ***************************************************************************/
170 
171     public override void preParseConfig ( IApplication app, ConfigParser config )
172     {
173         // Unused
174     }
175 
176 
177     /***************************************************************************
178 
179         Unused IConfigExtExtension method.
180 
181         We just need to provide an "empty" implementation to satisfy the
182         interface.
183 
184         Params:
185             app = the application instance
186             config = configuration instance
187             files = current list of configuration files to parse
188 
189         Returns:
190             new list of configuration files to parse
191 
192     ***************************************************************************/
193 
194     public override istring[] filterConfigFiles ( IApplication app,
195                                          ConfigParser config,
196                                          istring[] files )
197     {
198         // Unused
199         return files;
200     }
201 }
202 
203 
204 /*******************************************************************************
205 
206     An helper function to get the hostname, used by Collectd
207 
208     Returns:
209         A string representing the hostname
210 
211 *******************************************************************************/
212 
213 private istring getHostName ()
214 {
215     // SuSv2 ensure that hostname are <= 255 bytes
216     // On Linux, they are <= HOST_MAX_NAME which has been 64 bytes
217     // for almost all of Linux lifetime
218     char[256] buffer;
219     enforce!(ErrnoException)(gethostname(buffer.ptr, buffer.length) == 0);
220     return idup(buffer[0 .. strnlen(buffer.ptr, buffer.length)]);
221 
222 }
223 
224 private extern(C) size_t strnlen(Const!(char)* s, size_t maxlen);