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.stdc.gnu.string;
41 import ocean.sys.ErrnoException;
42 
43 import ocean.util.app.model.ExtensibleClassMixin;
44 import ocean.util.app.Application;
45 import ocean.util.app.ext.model.IConfigExtExtension;
46 import ocean.util.app.ext.model.ILogExtExtension;
47 import ocean.util.app.ext.ConfigExt;
48 
49 import ocean.util.app.ext.ReopenableFilesExt;
50 
51 import ocean.util.config.ConfigParser;
52 import ocean.util.log.Appender;
53 import ocean.util.log.Stats;
54 import ConfigFiller = ocean.util.config.ConfigFiller;
55 
56 import ocean.meta.types.Qualifiers;
57 import ocean.core.Verify;
58 import ocean.io.device.File;
59 
60 
61 /*******************************************************************************
62 
63     Application extension to parse configuration files for the stats output.
64 
65 *******************************************************************************/
66 
67 class StatsExt : IConfigExtExtension
68 {
69     /***************************************************************************
70 
71         Stats Log instance
72 
73     ***************************************************************************/
74 
75     public StatsLog stats_log;
76 
77 
78     /// Config instance for creating the default StatsLog.
79     public StatsLog.Config config;
80 
81 
82     /***************************************************************************
83 
84         Extension order. This extension uses -500 because it should be
85         called early, but after the LogExt extension.
86 
87         Returns:
88             the extension order
89 
90     ***************************************************************************/
91 
92     public override int order ( )
93     {
94         return -500;
95     }
96 
97 
98     /***************************************************************************
99 
100         Parse the configuration file options to set up the stats log.
101 
102         Params:
103             app = the application instance
104             parser = configuration instance
105 
106     ***************************************************************************/
107 
108     public override void processConfig ( IApplication app, ConfigParser parser )
109     {
110         ConfigFiller.fill!(StatsLog.Config)("STATS", this.config, parser);
111 
112         if (!this.config.app_name.length)
113             this.config.app_name = app.name;
114         if (!this.config.hostname.length)
115             this.config.hostname = getHostName();
116         if (!this.config.default_type.length)
117             this.config.default_type = this.config.app_name ~ "_stats";
118 
119         verify(this.config.app_name.length != 0);
120 
121         this.stats_log = this.newStatsLog(app, this.config);
122     }
123 
124 
125     /***************************************************************************
126 
127         Creates a new stats log instance according to the provided config
128         settings. If the reopenable files extension exists, the log file is
129         registered with it.
130 
131         Params:
132             app = the application instance
133             stats_config = stats log configuration instance
134 
135         Returns:
136             new, configured StatsLog instance
137 
138     ***************************************************************************/
139 
140     static public StatsLog newStatsLog ( IApplication app,
141         StatsLog.Config stats_config )
142     {
143         Appender newAppender ( istring file, Appender.Layout layout )
144         {
145             auto stream = new File(file, File.WriteAppending);
146 
147             if ( auto reopenable_files_ext =
148                 (cast(Application)app).getExtension!(ReopenableFilesExt) )
149             {
150                 reopenable_files_ext.register(stream);
151             }
152 
153             return new AppendStream(stream, true, layout);
154         }
155 
156         return new StatsLog(stats_config, &newAppender, stats_config.file_name);
157     }
158 
159     /***************************************************************************
160 
161         Unused IConfigExtExtension method.
162 
163         We just need to provide an "empty" implementation to satisfy the
164         interface.
165 
166         Params:
167             app = the application instance
168             config = configuration instance
169 
170     ***************************************************************************/
171 
172     public override void preParseConfig ( IApplication app, ConfigParser config )
173     {
174         // Unused
175     }
176 
177 
178     /***************************************************************************
179 
180         Unused IConfigExtExtension method.
181 
182         We just need to provide an "empty" implementation to satisfy the
183         interface.
184 
185         Params:
186             app = the application instance
187             config = configuration instance
188             files = current list of configuration files to parse
189 
190         Returns:
191             new list of configuration files to parse
192 
193     ***************************************************************************/
194 
195     public override istring[] filterConfigFiles ( IApplication app,
196                                          ConfigParser config,
197                                          istring[] files )
198     {
199         // Unused
200         return files;
201     }
202 }
203 
204 
205 /*******************************************************************************
206 
207     An helper function to get the hostname, used by Collectd
208 
209     Returns:
210         A string representing the hostname
211 
212 *******************************************************************************/
213 
214 private istring getHostName ()
215 {
216     // SuSv2 ensure that hostname are <= 255 bytes
217     // On Linux, they are <= HOST_MAX_NAME which has been 64 bytes
218     // for almost all of Linux lifetime
219     char[256] buffer;
220     enforce!(ErrnoException)(gethostname(buffer.ptr, buffer.length) == 0);
221     return idup(buffer[0 .. strnlen(buffer.ptr, buffer.length)]);
222 
223 }