<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>monitoring — jd:/dev/blog</title><description>Posts tagged &quot;monitoring&quot; on jd:/dev/blog.</description><link>https://julien.danjou.info/</link><item><title>Python Logging with Datadog</title><link>https://julien.danjou.info/blog/python-logging-with-datadog/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-logging-with-datadog/</guid><description>At Mergify, we generate a pretty large amount of logs. Every time an event is received from GitHub for a particular pull request, our engine computes a new state for it.</description><pubDate>Mon, 03 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt;, we generate a pretty large amount of logs. Every time an event is received from GitHub for a particular pull request, our engine computes a new state for it. Doing so, it logs some informational statements about what it&apos;s doing — and any error that might happen.&lt;/p&gt;
&lt;p&gt;This information is precious to us. Without proper logging, it&apos;d be utterly impossible for us to debug any issue. As we needed to store and index our logs somewhere, we picked Datadog as our log storage provider.&lt;/p&gt;
&lt;p&gt;Datadog offers real-time indexing of our logs. The ability to search our records that fast is compelling as we&apos;re able to retrieve log about a GitHub repository or a pull request with a single click.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/01/Screenshot-2020-01-06-at-17.23.58.png&quot; alt=&quot;Our custom Datadog log facets&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To achieve this result, we had to inject our Python application logs into Datadog. To set up the Python logging mechanism, we rely on &lt;a href=&quot;https://github.com/jd/daiquiri&quot;&gt;&lt;em&gt;daiquiri&lt;/em&gt;&lt;/a&gt;, a fantastic library I maintained for several years now. &lt;em&gt;Daiquiri&lt;/em&gt; leverages the regular Python &lt;code&gt;logging&lt;/code&gt; module, making its a no-brainer to set up and offering a few extra features.&lt;/p&gt;
&lt;p&gt;We recently added native support for the Datadog agent in &lt;em&gt;daiquiri&lt;/em&gt;, making it even more straightforward to log from your Python application.&lt;/p&gt;
&lt;h2&gt;Enabling log on the Datadog agent&lt;/h2&gt;
&lt;p&gt;Datadog has &lt;a href=&quot;https://docs.datadoghq.com/agent/logs/?tab=tailexistingfiles&quot;&gt;extensive documentation on how to configure its agent&lt;/a&gt;. This can be summarized to adding &lt;code&gt;logs_enabled: true&lt;/code&gt; in your agent configuration. Simple as that.&lt;/p&gt;
&lt;p&gt;You then need to create a new source for the agent. The easiest way to connect your application and the Datadog agent is using the TCP socket. Your application will write logs directly to the Datadog agent, which will forward the entries to Datadog backend.&lt;/p&gt;
&lt;p&gt;Create a configuration file in &lt;code&gt;conf.d/python.d/conf.yaml&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;h2&gt;Setting up &lt;code&gt;daiquiri&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Once this is done, you need to configure your Python application to log to the TCP socket configured in the agent above.&lt;/p&gt;
&lt;p&gt;The Datadog agent expects logs in JSON format being sent, which is what &lt;em&gt;daiquiri&lt;/em&gt; does for you. Using JSON allows to embed any extra fields to leverage fast search and indexing. As &lt;em&gt;daiquiri&lt;/em&gt; provides native handling for extra fields, you&apos;ll be able to send those extra fields without trouble.&lt;/p&gt;
&lt;p&gt;First, list &lt;em&gt;daiquiri&lt;/em&gt; in your application dependency. Then, set up logging in your application this way:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import daiquiri

daiquiri.setup(
  outputs=[
    daiquiri.output.Datadog(),
  ],
  level=logging.INFO,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration logs to the default TCP destination &lt;code&gt;localhost:10518&lt;/code&gt; — though you can pass the &lt;code&gt;host&lt;/code&gt; and &lt;code&gt;port&lt;/code&gt; argument to change that. You can customize the outputs as you wish by checking out &lt;a href=&quot;https://daiquiri.readthedocs.io/en/latest/&quot;&gt;daiquiri documentation&lt;/a&gt;. For example, you could also include logging to &lt;code&gt;stdout&lt;/code&gt; by adding &lt;code&gt;daiquiri.output.Stream(sys.stdout)&lt;/code&gt; in the output list.&lt;/p&gt;
&lt;h2&gt;Using &lt;code&gt;extra&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;When using &lt;em&gt;daiquiri&lt;/em&gt;, you&apos;re free to use &lt;code&gt;logging.getLogger&lt;/code&gt; to get your regular logging object. However, by using the alternative &lt;code&gt;daiquiri.getLogger&lt;/code&gt; function, you&apos;re enabling the native use of extra arguments — which is quite handy. That means you can pass any arbitrary key/value to your log call, and see it up being embedded in your log data — up to Datadog.&lt;/p&gt;
&lt;p&gt;Here&apos;s an example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import daiquiri

[…]

log = daiquiri.getLogger(__name__)
log.info(&quot;User did something important&quot;, user=user, request_id=request_id)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The extra keyword argument passed to &lt;code&gt;log.info&lt;/code&gt; will be directly shown as attributes in Datadog logs:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/01/Screenshot-2020-01-06-at-18.22.04.png&quot; alt=&quot;One of the log line of our Mergify engine&quot; /&gt;&lt;/p&gt;
&lt;p&gt;All those attributes can then be used to search or to display custom views. This is really powerful to monitor and debug any kind of service.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/01/Screenshot-2020-01-06-at-18.39.05.png&quot; alt=&quot;Screenshot of Datadog log explorer showing custom attributes for search and display&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;A log object per object&lt;/h2&gt;
&lt;p&gt;When passing &lt;em&gt;extra&lt;/em&gt; arguments, it is easy to make mistakes and forget some. This especially can happen when your application wants to log information for a particular object.&lt;/p&gt;
&lt;p&gt;The best pattern to avoid this is to create a custom log object per object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import daiquiri

class MyObject:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.log = daiquiri.getLogger(&quot;MyObject&quot;, x=self.x, y=self.y)

    def do_something(self):
        try:
            self.call_this()
        except Exception:
            self.log.error(&quot;Something bad happened&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using the &lt;code&gt;self.log&lt;/code&gt; object as defined above, there&apos;s no way for your application to miss some extra fields for an object. All your logs will look in the same style and will end up being indexed correctly in Datadog.&lt;/p&gt;
&lt;h2&gt;Log Design&lt;/h2&gt;
&lt;p&gt;The &lt;em&gt;extra&lt;/em&gt; arguments from the Python loggers are often dismissed, and many developers stick to logging strings with various information included inside. Having a proper explanation string, plus a few extra key/value pairs that are parsable by machines and humans, is a better way to do logging. Leveraging engines such as Datadog allow to store and query those logs in a snap.&lt;/p&gt;
&lt;p&gt;This is way more efficient than trying to parse and grep strings yourselves!&lt;/p&gt;
</content:encoded><category>python</category><category>mergify</category><category>monitoring</category></item><item><title>Gnocchi or Prometheus?</title><link>https://julien.danjou.info/blog/gnocchi-or-prometheus/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-or-prometheus/</guid><description>The realm of time series database keeps expanding those last years. Now and then a new contender appears from the fog. People keep asking me about the difference between Gnocchi and Prometheus.</description><pubDate>Wed, 30 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The realm of time series database keeps expanding those last years. Now and then a new contender appears from the fog. People keep asking me about the difference between &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; and &lt;a href=&quot;http://prometheus.io&quot;&gt;Prometheus&lt;/a&gt;. It&apos;s time to content them.&lt;/p&gt;
&lt;p&gt;Gnocchi and Prometheus are two open source projects evolving in the same expertise area, time series handling. They both are licensed under the &lt;strong&gt;Apache 2.0 license&lt;/strong&gt; (see &lt;a href=&quot;https://github.com/gnocchixyz/gnocchi/blob/master/LICENSE&quot;&gt;Gnocchi license file&lt;/a&gt; and &lt;a href=&quot;https://github.com/prometheus/prometheus/blob/master/LICENSE&quot;&gt;Prometheus license file&lt;/a&gt;. And that&apos;s a good thing!&lt;/p&gt;
&lt;p&gt;Both Gnocchi and Prometheus offers a bunch of features. Here&apos;s a table summary of the differences between the features they both offer – or not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Prometheus&lt;/p&gt;
&lt;p&gt;Gnocchi&lt;/p&gt;
&lt;p&gt;Multi-tenant&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;User auth &amp;amp; ACL&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Resource history&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Metric polling&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;Highly available&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Horizontal scalability&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Alerting engine&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;Data compression&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Pre-computed aggregation&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Grafana support&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;collectd support&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;#comparison th, #comparison td + td { text-align: center; }&lt;/p&gt;
&lt;p&gt;There&apos;s a lot of overlap between the two projects, but there are also some major differences.&lt;/p&gt;
&lt;p&gt;First, Gnocchi does not try to solve the metric retrieval problem. Prometheus provides a pull mechanism and takes in charge of getting the measurements. Gnocchi developers estimate that they are plenty of tools already doing that and that work well, such as &lt;a href=&quot;http://collectd.org&quot;&gt;collectd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_siren.png&quot; alt=&quot;Alert siren icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Secondly, Prometheus offers an &lt;a href=&quot;https://prometheus.io/docs/alerting/overview/&quot;&gt;alerting engine&lt;/a&gt;, statically configured with&lt;br /&gt;
a YAML file. It is way better than Gnocchi which offers nothing in comparison – for now. Gnocchi developers &lt;a href=&quot;https://github.com/gnocchixyz/gnocchi/issues/71&quot;&gt;are discussing the feature&lt;/a&gt; and while it&apos;s not on the roadmap yet, it will happen. It will, however, leverage a REST API to be controlled, as it seems important to us to be able to define alerts&lt;br /&gt;
programmatically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_storage.png&quot; alt=&quot;Storage icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then there is a bunch of features where Gnocchi shines compared to Prometheus, and it is the core of its function: storing metrics. Gnocchi has a great storage engine that supports many storage backends (plain&lt;br /&gt;
files, &lt;a href=&quot;https://docs.openstack.org/swift/latest/&quot;&gt;OpenStack Swift&lt;/a&gt;, &lt;a href=&quot;http://ceph.org&quot;&gt;Ceph&lt;/a&gt;…). It helps Gnocchi scaling horizontally and providing native high-availability, whereas Prometheus stays a single point of failure.&lt;/p&gt;
&lt;p&gt;Multi-tenant and authentication are also supported by Gnocchi, allowing a single instance to be shared by multiple accounts. System administrators do not commonly use this kind of feature, but applications developers usually need them.&lt;/p&gt;
&lt;p&gt;That brings me to the usage and querying of Prometheus and Gnocchi. Prometheus has its small DSL (referred to as &lt;a href=&quot;https://prometheus.io/docs/querying/basics/&quot;&gt;PromQL&lt;/a&gt;) whereas Gnocchi has a &lt;a href=&quot;http://gnocchi.xyz/rest.html&quot;&gt;fully featured REST API&lt;/a&gt; that tries to expose proper semantic. It does not seem there are major differences between the two in term of features.&lt;/p&gt;
&lt;p&gt;Both Prometheus and Gnocchi support aggregating values over time ranges on query time (&quot;give me the minimum value for every 5 minutes range over the last day&quot;). Gnocchi always aggregates metrics at writing time, and never at query time (unless doing it cross-metrics). This implies that Gnocchi needs a bit of CPU time at write time to pre-compute those aggregates, but it is blazingly fast at reading time as it has nothing to compute. Prometheus can do the same thing using &lt;a href=&quot;https://prometheus.io/docs/querying/rules/&quot;&gt;recording rules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_clock.png&quot; alt=&quot;Clock icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Prometheus has some limitations inherent to time series database designed around the notion of &quot;monitoring&quot;: they tend to compute everything relatively to &lt;code&gt;$NOW&lt;/code&gt;. For example, it seems impossible to inject data from the past. The timestamp for a value is the timestamp where Prometheus read that value. If Prometheus misses values for a few hours, don&apos;t think about importing it back.&lt;/p&gt;
&lt;p&gt;I&apos;m noting this here as it makes it harder to benchmark Prometheus for ingestion. You need tons of fake metrics to polls and build data. I did not find any reference of Prometheus performances online, though it is advertised to ingest &quot;millions of measures from thousands of sources&quot;.&lt;/p&gt;
&lt;p&gt;Query performances seem to vary on Prometheus, and I did not find any benchmark on that neither. Gnocchi leverages standard RDBMS (MySQL or PostgreSQL is supported) to query indexed data and the metrics retrieval is always &lt;em&gt;O(1)&lt;/em&gt;, making it &lt;strong&gt;always fast&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If you look in different and older areas, there never has been only one HTTP server. Many people use Apache HTTP server, but you&apos;ll find plenty of users of nginx, Tomcat, HAProxy, Node.js or uwsgi which are also common options nowadays. Same goes for RDBMS if you look at PostgreSQL, MySQL and other databases solution, etc. There will never be a project winning all the market share.&lt;/p&gt;
&lt;p&gt;It seems to me that time series storage and management is also growing in this category. There will probably be various projects that will enjoy some popularity and growth. Every project addresses the time series problem space with a different view and different trade-offs. There might never be a single project solving all problems at once.&lt;/p&gt;
&lt;p&gt;Prometheus seems to be oriented toward monitoring of live systems. Gnocchi is oriented to highly available time series storage at massive scale. Not considering performances (I was not able to compare anyway), both have different tradeoffs in term of features, philosophy, and orientation. Depending on your use cases, one might be a better fit than the other.&lt;/p&gt;
</content:encoded><category>gnocchi</category><category>monitoring</category></item><item><title>Sending your collectd metrics to Gnocchi</title><link>https://julien.danjou.info/blog/gnocchi-collectd-setup/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-collectd-setup/</guid><description>Knowing that collectd is a daemon that collects system and applications metrics and that Gnocchi is a scalable timeseries database, it sounds like a good idea to combine them together.</description><pubDate>Thu, 16 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Knowing that &lt;a href=&quot;http://collectd.org/&quot;&gt;collectd&lt;/a&gt; is a daemon that collects system and applications metrics and that &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; is a scalable timeseries database, it sounds like a good idea to combine them together. &lt;em&gt;Cherry on the cake&lt;/em&gt;: you can easily draw charts using &lt;a href=&quot;http://grafana.org&quot;&gt;Grafana&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While it&apos;s true that Gnocchi is well integrated with &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt;, as it orginally comes from this ecosystem, it actually works standalone by default. Starting with the 3.1 version, it is now easy to send metrics to &lt;em&gt;Gnocchi&lt;/em&gt; using &lt;em&gt;collectd&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;What we&apos;ll need to install to accomplish this task is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;collectd&lt;/li&gt;
&lt;li&gt;Gnocchi&lt;/li&gt;
&lt;li&gt;collectd-gnocchi&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How you install them does not really matter. If they are packaged by your operating system, go ahead. For Gnocchi and collectd-gnocchi, you can also use &lt;em&gt;pip&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## pip install gnocchi[file,postgresql]
[…]
Successfully installed gnocchi-3.1.0
## pip install collectd-gnocchi
Collecting collectd-gnocchi
  Using cached collectd-gnocchi-1.0.1.tar.gz
[…]
Installing collected packages: collectd-gnocchi
  Running setup.py install for collectd-gnocchi ... done
Successfully installed collectd-gnocchi-1.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The detailed installation procedure for Gnocchi is &lt;a href=&quot;http://gnocchi.xyz/install.html#id1&quot;&gt;detailed in the documentation&lt;/a&gt;. It among other things explains which flavors are available – here I picked PostgreSQL and the file driver to store the metrics.&lt;/p&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;h3&gt;Gnocchi&lt;/h3&gt;
&lt;p&gt;Gnocchi is simple to configure and is again &lt;a href=&quot;http://gnocchi.xyz/configuration.html&quot;&gt;documented&lt;/a&gt;. The default configuration file is &lt;code&gt;/etc/gnocchi/gnocchi.conf&lt;/code&gt; – you can generate it with &lt;code&gt;gnocchi-config-generator&lt;/code&gt; if needed. However, it also possible to specify another configuration file by appending the &lt;code&gt;--config-file&lt;/code&gt; option to any command line&lt;/p&gt;
&lt;p&gt;In Gnocchi&apos;s configuration file, you need to set the &lt;code&gt;indexer.url&lt;/code&gt; configuration option to point an existing PostgreSQL database and set &lt;code&gt;storage.file_basepath&lt;/code&gt; to an existing directory to store your metrics (the default is &lt;code&gt;/var/lib/gnocchi&lt;/code&gt;). That gives something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[indexer]
url = postgresql://root:p4assw0rd@localhost/gnocchi

[storage]
file_basepath = /var/lib/gnocchi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once done, just run the &lt;code&gt;gnocchi-upgrade&lt;/code&gt; command to initialize the index and storage.&lt;/p&gt;
&lt;h3&gt;collectd&lt;/h3&gt;
&lt;p&gt;Collectd provides a default configuration file that loads a bunch of plugin by default, that will meter all sort of metrics on your computer. You can check the &lt;a href=&quot;http://collectd.org/documentation.shtml&quot;&gt;documentation&lt;/a&gt; online to see how to disable or enable plugins.&lt;/p&gt;
&lt;p&gt;As the &lt;em&gt;collectd-gnocchi&lt;/em&gt; plugin is written in Python, you&apos;ll need to enable the Python plugin and load the &lt;em&gt;collectd-gnocchi&lt;/em&gt; module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LoadPlugin python

&amp;lt;Plugin python&amp;gt;
  Import &quot;collectd_gnocchi&quot;
  &amp;lt;Module collectd_gnocchi&amp;gt;
      endpoint &quot;http://localhost:8041&quot;
  &amp;lt;/Module&amp;gt;
&amp;lt;/Plugin&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is enough to enable the storage of metrics in Gnocchi.&lt;/p&gt;
&lt;h2&gt;Running the daemons&lt;/h2&gt;
&lt;p&gt;Once everything is configured, you can launch &lt;code&gt;gnocchi-metricd&lt;/code&gt; and the &lt;code&gt;gnocchi-api&lt;/code&gt; daemon:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gnocchi-metricd
2017-01-26 15:22:49.018 15971 INFO gnocchi.cli [-] 0 measurements bundles
across 0 metrics wait to be processed.
[…]
## In another terminal
$ gnocchi-api --port 8041
[…]
STARTING test server gnocchi.rest.app.build_wsgi_app
Available at http://127.0.0.1:8041/
[…]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s not recommended to run Gnocchi using Gnocchi API (as&lt;br /&gt;
&lt;a href=&quot;http://gnocchi.xyz/running.html#running-as-a-wsgi-application&quot;&gt;written in the documentation&lt;/a&gt;): using &lt;a href=&quot;https://uwsgi-docs.readthedocs.io/&quot;&gt;uwsgi&lt;/a&gt; is a better option. However for rapid testing, the &lt;code&gt;gnocchi-api&lt;/code&gt; daemon is good enough.&lt;/p&gt;
&lt;p&gt;Once that&apos;s done, you can start &lt;code&gt;collectd&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ collectd
## Or to run in foreground with a different configuration file:
## $ collectd -C collectd.conf -f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have any problem launchding &lt;em&gt;colllectd&lt;/em&gt;, check syslog for more information: there might be an issue loading a module or plugin.&lt;/p&gt;
&lt;p&gt;If no error are printed, then everythin&apos;s working fine and you soon should see &lt;em&gt;gnocchi-api&lt;/em&gt; printing some requests such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.0.0.1 - - [26/Jan/2017 15:27:03] &quot;POST /v1/resource/collectd HTTP/1.1&quot; 409 113
127.0.0.1 - - [26/Jan/2017 15:27:03] &quot;POST /v1/batch/resources/metrics/measures?create_metrics=True HTTP/1.1&quot; 400 91
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Enjoying the result&lt;/h2&gt;
&lt;p&gt;Once everything runs, you can access your newly created resources and metric by using the &lt;a href=&quot;http://pypi.python.org/pypi/gnocchiclient&quot;&gt;gnocchiclient&lt;/a&gt;. It should have been installed as a dependency of &lt;em&gt;collectd_gnocchi&lt;/em&gt;, but you can also install it manually using &lt;code&gt;pip install gnocchiclient&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you need to specify a different endpoint you can use the &lt;code&gt;--endpoint&lt;/code&gt; option (which default to &lt;a href=&quot;http://localhost:8041&quot;&gt;http://localhost:8041&lt;/a&gt;). Do not hesitate to check the &lt;code&gt;--help&lt;/code&gt; option for more information.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gnocchi resource list --details
+---------------+----------+------------+---------+----------------------+---------------+----------+----------------+--------------+---------+-----------+
| id            | type     | project_id | user_id | original_resource_id | started_at    | ended_at | revision_start | revision_end | creator | host      |
+---------------+----------+------------+---------+----------------------+---------------+----------+----------------+--------------+---------+-----------+
| dd245138-00c7 | collectd | None       | None    | dd245138-00c7-5bdc-  | 2017-01-26T14 | None     | 2017-01-26T14: | None         | admin   | localhost |
| -5bdc-94f8-26 |          |            |         | 94f8-263e236812f7    | :21:02.297466 |          | 21:02.297483+0 |              |         |           |
| 3e236812f7    |          |            |         |                      | +00:00        |          | 0:00           |              |         |           |
+---------------+----------+------------+---------+----------------------+---------------+----------+----------------+--------------+---------+-----------+
$ gnocchi resource show collectd:localhost
+-----------------------+-----------------------------------------------------------------------+
| Field                 | Value                                                                 |
+-----------------------+-----------------------------------------------------------------------+
| created_by_project_id |                                                                       |
| created_by_user_id    | admin                                                                 |
| creator               | admin                                                                 |
| ended_at              | None                                                                  |
| host                  | localhost                                                             |
| id                    | dd245138-00c7-5bdc-94f8-263e236812f7                                  |
| metrics               | interface-en0@if_errors-0: 5d60f224-2e9e-4247-b415-64d567cf5866       |
|                       | interface-en0@if_errors-1: 1df8b08b-555a-4cab-9186-f9b79a814b03       |
|                       | interface-en0@if_octets-0: 491b7517-7219-4a04-bdb6-934d3bacb482       |
|                       | interface-en0@if_octets-1: 8b5264b8-03f3-4aba-a7f8-3cd4b559e162       |
|                       | interface-en0@if_packets-0: 12efc12b-2538-45e7-aa66-f8b9960b5fa3      |
|                       | interface-en0@if_packets-1: 39377ff7-06e8-454a-a22a-942c8f2bca56      |
|                       | interface-en1@if_errors-0: c3c7e9fc-f486-4d0c-9d36-55cea855596a       |
|                       | interface-en1@if_errors-1: a90f1bec-3a60-4f58-a1d1-b3c09dce4359       |
|                       | interface-en1@if_octets-0: c1ee8c75-95bf-4096-8055-8c0c4ec8cd47       |
|                       | interface-en1@if_octets-1: cbb90a94-e133-4deb-ac10-3f37770e32f0       |
|                       | interface-en1@if_packets-0: ac93b1b9-da71-4876-96aa-76067b35c6c9      |
|                       | interface-en1@if_packets-1: 2f8528b2-12ae-4c4d-bec7-8cc987e7487b      |
|                       | interface-en2@if_errors-0: ddcf7203-4c49-400b-9320-9d3e0a63c6d5       |
|                       | interface-en2@if_errors-1: b249ea42-01ad-4742-9452-2c834010df71       |
|                       | interface-en2@if_octets-0: 8c23013a-604e-40bf-a07a-e2dc4fc5cbd7       |
|                       | interface-en2@if_octets-1: 806c1452-0607-4b56-b184-c4ffd48f52c0       |
|                       | interface-en2@if_packets-0: c5bc6103-6313-4b8b-997d-01930d1d8af4      |
|                       | interface-en2@if_packets-1: 478ae87e-e56b-44e4-83b0-ed28d99ed280      |
|                       | load@load-0: 5db2248d-2dca-401e-b2e2-bbaee23b623e                     |
|                       | load@load-1: 6f74ac93-78fd-4a74-a47e-d2add487a30f                     |
|                       | load@load-2: 1897aca1-356e-4791-907f-512e516992b5                     |
|                       | memory@memory-active-0: 83944a85-9c84-4fe4-b471-1a6cf8dce858          |
|                       | memory@memory-free-0: 0ccc7cfa-26a5-4441-a15f-9ebb2aa82c6d            |
|                       | memory@memory-inactive-0: 63736026-94c4-47c5-8d6f-a9d89d65025b        |
|                       | memory@memory-wired-0: b7217fd6-2cdc-4efd-b1a8-a1edd52eaa2e           |
| original_resource_id  | dd245138-00c7-5bdc-94f8-263e236812f7                                  |
| project_id            | None                                                                  |
| revision_end          | None                                                                  |
| revision_start        | 2017-01-26T14:21:02.297483+00:00                                      |
| started_at            | 2017-01-26T14:21:02.297466+00:00                                      |
| type                  | collectd                                                              |
| user_id               | None                                                                  |
+-----------------------+-----------------------------------------------------------------------+
% gnocchi metric show -r collectd:localhost load@load-0
+------------------------------------+-----------------------------------------------------------------------+
| Field                              | Value                                                                 |
+------------------------------------+-----------------------------------------------------------------------+
| archive_policy/aggregation_methods | min, std, sum, median, mean, 95pct, count, max                        |
| archive_policy/back_window         | 0                                                                     |
| archive_policy/definition          | - timespan: 1:00:00, granularity: 0:05:00, points: 12                 |
|                                    | - timespan: 1 day, 0:00:00, granularity: 1:00:00, points: 24          |
|                                    | - timespan: 30 days, 0:00:00, granularity: 1 day, 0:00:00, points: 30 |
| archive_policy/name                | low                                                                   |
| created_by_project_id              |                                                                       |
| created_by_user_id                 | admin                                                                 |
| creator                            | admin                                                                 |
| id                                 | 5db2248d-2dca-401e-b2e2-bbaee23b623e                                  |
| name                               | load@load-0                                                           |
| resource/created_by_project_id     |                                                                       |
| resource/created_by_user_id        | admin                                                                 |
| resource/creator                   | admin                                                                 |
| resource/ended_at                  | None                                                                  |
| resource/id                        | dd245138-00c7-5bdc-94f8-263e236812f7                                  |
| resource/original_resource_id      | dd245138-00c7-5bdc-94f8-263e236812f7                                  |
| resource/project_id                | None                                                                  |
| resource/revision_end              | None                                                                  |
| resource/revision_start            | 2017-01-26T14:21:02.297483+00:00                                      |
| resource/started_at                | 2017-01-26T14:21:02.297466+00:00                                      |
| resource/type                      | collectd                                                              |
| resource/user_id                   | None                                                                  |
| unit                               | None                                                                  |
+------------------------------------+-----------------------------------------------------------------------+
$ gnocchi measures show -r collectd:localhost load@load-0
+---------------------------+-------------+--------------------+
| timestamp                 | granularity |              value |
+---------------------------+-------------+--------------------+
| 2017-01-26T00:00:00+00:00 |     86400.0 | 3.2705004391254193 |
| 2017-01-26T15:00:00+00:00 |      3600.0 | 3.2705004391254193 |
| 2017-01-26T15:00:00+00:00 |       300.0 | 2.6022800611413044 |
| 2017-01-26T15:05:00+00:00 |       300.0 |  3.561742940080275 |
| 2017-01-26T15:10:00+00:00 |       300.0 | 2.5605337960379466 |
| 2017-01-26T15:15:00+00:00 |       300.0 |  3.837517851142473 |
| 2017-01-26T15:20:00+00:00 |       300.0 | 3.9625948392427883 |
| 2017-01-26T15:25:00+00:00 |       300.0 | 3.2690042162698414 |
+---------------------------+-------------+--------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the command line works smoothly and can show you any kind of metric reported by &lt;em&gt;collectd&lt;/em&gt;. In this case, it was just running on my laptop, but you can imagine it&apos;s easy enough to poll thousands of hosts with &lt;em&gt;collectd&lt;/em&gt; and &lt;em&gt;Gnocchi&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Bonus: charting with Grafana&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://grafana.org&quot;&gt;Grafana&lt;/a&gt;, a charting software, has a plugin for &lt;em&gt;Gnocchi&lt;/em&gt; as &lt;a href=&quot;http://gnocchi.xyz/grafana.html&quot;&gt;detailed in the documentation&lt;/a&gt;. Once installed, you can just configure &lt;em&gt;Grafana&lt;/em&gt; to point to &lt;em&gt;Gnocchi&lt;/em&gt; this way:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/grafana-config-screen-gnocchi.png&quot; alt=&quot;Screenshot of Grafana configuration screen pointing to Gnocchi as data source&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can then create a new dashboard by filling the forms as you wish. See this other screenshot for a nice example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/grafana-gnocchi-load.png&quot; alt=&quot;Charts of my laptop&apos;s load average&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I hope everything is clear and easy enough. If you have any question, feel free to write something in the comment section!&lt;/p&gt;
</content:encoded><category>gnocchi</category><category>monitoring</category></item><item><title>Gnocchi talk at the Paris Monitoring Meetup #6</title><link>https://julien.danjou.info/blog/paris-monitoring-6-gnocchi/</link><guid isPermaLink="true">https://julien.danjou.info/blog/paris-monitoring-6-gnocchi/</guid><description>Last week was the sixth edition of the Paris Monitoring Meetup, where I was invited as a speaker to present and talk about Gnocchi.</description><pubDate>Fri, 27 May 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week was the sixth edition of the &lt;a href=&quot;http://www.meetup.com/Paris-Monitoring/events/230515751/&quot;&gt;Paris Monitoring Meetup&lt;/a&gt;, where I was invited as a speaker to present and talk about &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/paris-monitoring.png&quot; alt=&quot;paris-monitoring&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There was around 50 persons in the room, listening to my presentation of Gnocchi.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/jd-gnocchi-paris-monitoring-meetup-6.jpg&quot; alt=&quot;jd-gnocchi-paris-monitoring-meetup-6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The talk went fine and I had a few interesting questions and feedback. One interesting point that keeps coming when talking about Gnocchi, is its OpenStack label, which scares away a lot of people. We definitely need to continue explaining that the project work stand-alone has a no dependency on OpenStack, just a great integration with it.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://www.monitoring-fr.org/&quot;&gt;Monitoring-fr&lt;/a&gt; organization also &lt;a href=&quot;http://www.monitoring-fr.org/2016/05/meetup-paris-monitoring-6-interview-de-julien-danjou-pour-gnocchi-metric-as-a-service/&quot;&gt;interviewed me&lt;/a&gt; after the meetup about Gnocchi. The interview is in French, obviously. I talk about Gnocchi, what it does, how it does it and why we started the project a couple of years ago. Enjoy, and let me know what you think!&lt;/p&gt;
</content:encoded><category>talks</category><category>monitoring</category><category>gnocchi</category><category>openstack</category></item><item><title>Visualize your OpenStack cloud: Gnocchi &amp; Grafana</title><link>https://julien.danjou.info/blog/openstack-gnocchi-grafana/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-gnocchi-grafana/</guid><description>We&apos;ve been hard working with the Gnocchi team these last months to store your metrics, and I guess it&apos;s time to show off a bit.  So far Gnocchi offers scalable metric storage and resource indexation,</description><pubDate>Mon, 14 Sep 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We&apos;ve been hard working with the Gnocchi team these last months to store your metrics, and I guess it&apos;s time to show off a bit.&lt;/p&gt;
&lt;p&gt;So far Gnocchi offers scalable metric storage and resource indexation, especially for OpenStack cloud – but not only, we&apos;re generic. It&apos;s cool to store metrics, but it can be even better to have a way to visualize them!&lt;/p&gt;
&lt;h2&gt;Prototyping&lt;/h2&gt;
&lt;p&gt;We very soon started to build a little HTML interface. Being REST-friendly guys, we enabled it on the same endpoints that were being used to retrieve information and measures about metric, sending back &lt;code&gt;text/html&lt;/code&gt; instead of &lt;code&gt;application/json&lt;/code&gt; if you were requesting those pages from a Web browser.&lt;/p&gt;
&lt;p&gt;But let&apos;s face it: we are back-end developers, we suck at any kind front-end development. CSS, HTML, JavaScript? Bwah! So what we built was a starting point, hoping some magical Web developer would jump in and finish the job.&lt;/p&gt;
&lt;p&gt;Obviously it never happened.&lt;/p&gt;
&lt;h2&gt;Ok, so what&apos;s out there?&lt;/h2&gt;
&lt;p&gt;It turns out there are back-end agnostic solutions out there, and we decided to pick &lt;a href=&quot;http://grafana.org&quot;&gt;Grafana&lt;/a&gt;. Grafana is a complete graphing dashboard solution that can be plugged on top of any back-end. It already supports timeseries databases such as Graphite, InfluxDB and OpenTSDB.&lt;/p&gt;
&lt;p&gt;That was largely enough for that my fellow developer &lt;a href=&quot;https://blog.sileht.net/&quot;&gt;Mehdi Abaakouk&lt;/a&gt; to jump in and start writing a Gnocchi plugin for Grafana! Consequently, there is now a basic but solid and working back-end for Grafana that lies in the &lt;em&gt;&lt;a href=&quot;https://github.com/grafana/grafana-plugins/tree/master/datasources/gnocchi&quot;&gt;grafana-plugins&lt;/a&gt;&lt;/em&gt;&lt;br /&gt;
repository.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-grafana.png&quot; alt=&quot;gnocchi-grafana&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With that plugin, you can graph anything that is stored in Gnocchi, from raw metrics to metrics tied to resources. You can use templating, but no annotation yet.&lt;/p&gt;
&lt;p&gt;The back-end supports Gnocchi with or without Keystone involved, and any type of authentication (basic auth or Keystone token). So yes, it even works if you&apos;re not running Gnocchi with the rest of OpenStack.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-grafana-group.png&quot; alt=&quot;gnocchi-grafana-group&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It also supports advanced queries, so you can search for resources based on some criterion and graphs their metrics.&lt;/p&gt;
&lt;h2&gt;I want to try it!&lt;/h2&gt;
&lt;p&gt;If you want to deploy it, all you need to do is to install Grafana and its plugins, and create a new datasource pointing to Gnocchi. It is that simple. There&apos;s some CORS middleware configuration involved if you&apos;re planning on using Keystone authentication, but it&apos;s pretty straightforward – just set the &lt;code&gt;cors.allowed_origin&lt;/code&gt; option to the URL of your Grafana dashboard.&lt;/p&gt;
&lt;p&gt;We added support of Grafana directly in Gnocchi devstack plugin. If you&apos;re running &lt;a href=&quot;http://devstack.org&quot;&gt;DevStack&lt;/a&gt; you can follow &lt;a href=&quot;http://docs.openstack.org/developer/gnocchi/devstack.html&quot;&gt;the instructions&lt;/a&gt; – which are basically adding the line &lt;code&gt;enable_service gnocchi-grafana&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Moving to Grafana core&lt;/h2&gt;
&lt;p&gt;[Mehdi just opened a pull request] (&lt;a href=&quot;https://github.com/grafana/grafana/pull/2716&quot;&gt;https://github.com/grafana/grafana/pull/2716&lt;/a&gt;) a few days ago to merge the plugin into Grafana core. It&apos;s actually one of the most unit-tested plugin in Grafana so far, so it should be on a good path to be merged in the future and have support of Gnocchi directly into Grafana without any plugin involved.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/grafana-gnocchi-unittests.png&quot; alt=&quot;grafana-gnocchi-unittests&quot; /&gt;&lt;/p&gt;
</content:encoded><category>gnocchi</category><category>openstack</category><category>monitoring</category></item><item><title>Python bad practice, a concrete case</title><link>https://julien.danjou.info/blog/python-bad-practice-concrete-case/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-bad-practice-concrete-case/</guid><description>A lot of people read up on good Python practice, and there&apos;s plenty of information about that on the Internet. Many tips are included in the book I wrote this year, The Hacker&apos;s Guide to Python.</description><pubDate>Mon, 15 Sep 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A lot of people read up on good Python practice, and there&apos;s plenty of information about that on the Internet. Many tips are included in the book I wrote this year, &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;. Today I&apos;d like to show a concrete case of code that I don&apos;t consider being the state of the art.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/python-thumb-down.png&quot; alt=&quot;python-thumb-down&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://julien.danjou.info/blog/openstack-ceilometer-the-gnocchi-experiment&quot;&gt;last article&lt;/a&gt; where I talked about my new project Gnocchi, I wrote about how I tested, hacked and then ditched &lt;em&gt;&lt;a href=&quot;http://graphite.wikidot.com/whisper&quot;&gt;whisper&lt;/a&gt;&lt;/em&gt; out. Here I&apos;m going to explain part of my thought process and a few things that raised my eyebrows when hacking this code.&lt;/p&gt;
&lt;p&gt;Before I start, please don&apos;t get the spirit of this article wrong. It&apos;s in no way a personal attack to the authors and contributors (who I don&apos;t know). Furthermore, &lt;em&gt;whisper&lt;/em&gt; is a piece of code that is in production in thousands of installation, storing metrics for years. While I can argue that I consider the code not to be following best practice, it definitely works well enough and is worthy to a lot of people.&lt;/p&gt;
&lt;h2&gt;Tests&lt;/h2&gt;
&lt;p&gt;The first thing that I noticed when trying to hack on &lt;em&gt;whisper&lt;/em&gt;, is the lack of test. There&apos;s only one file containing tests, named &lt;code&gt;test_whisper.py&lt;/code&gt;, and the coverage it provides is pretty low. One can check that using the &lt;em&gt;coverage&lt;/em&gt; tool.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ coverage run test_whisper.py
...........
----------------------------------------------------------------------
Ran 11 tests in 0.014s

OK
$ coverage report
Name           Stmts   Miss  Cover
----------------------------------
test_whisper     134      4    97%
whisper          584    227    61%
----------------------------------
TOTAL            718    231    67%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While one would think that 61% is &quot;not so bad&quot;, taking a quick peak at the actual test code shows that the tests are incomplete. Why I mean by incomplete is that they for example use the library to store values into a database, but they never check if the results can be fetched and if the fetched results are accurate. Here&apos;s a good reason one should never blindly trust the test cover percentage as a quality metric.&lt;/p&gt;
&lt;p&gt;When I tried to modify &lt;em&gt;whisper&lt;/em&gt;, as the tests do not check the entire cycle of the values fed into the database, I ended up doing wrong changes but had the tests still pass.&lt;/p&gt;
&lt;h2&gt;No PEP 8, no Python 3&lt;/h2&gt;
&lt;p&gt;The code doesn&apos;t respect PEP 8 . A run of &lt;a href=&quot;https://flake8.readthedocs.org/&quot;&gt;flake8&lt;/a&gt; + &lt;a href=&quot;https://pypi.python.org/pypi/hacking&quot;&gt;hacking&lt;/a&gt; shows 732 errors… While it does not impact the code itself, it&apos;s more painful to hack on it than it is on most Python projects.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;hacking&lt;/em&gt; tool also shows that the code is not Python 3 ready as there is usage of Python 2 only syntax.&lt;/p&gt;
&lt;p&gt;A good way to fix that would be to set up &lt;a href=&quot;https://testrun.org/tox/latest/&quot;&gt;tox&lt;/a&gt; and adds a few targets for PEP 8 checks and Python 3 tests. Even if the test suite is not complete, starting by having flake8 run without errors and the few unit tests working with Python 3 should put the project in a better light.&lt;/p&gt;
&lt;h2&gt;Not using idiomatic Python&lt;/h2&gt;
&lt;p&gt;A lot of the code could be simplified by using idiomatic Python. Let&apos;s take a simple example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def fetch(path,fromTime,untilTime=None,now=None):
  fh = None
  try:
    fh = open(path,&apos;rb&apos;)
    return file_fetch(fh, fromTime, untilTime, now)
  finally:
    if fh:
      fh.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That piece of code could be easily rewritten as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def fetch(path,fromTime,untilTime=None,now=None):
  with open(path, &apos;rb&apos;) as fh:
    return file_fetch(fh, fromTime, untilTime, now)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, the function looks actually so simple that one can even wonder why it should exists – but why not.&lt;/p&gt;
&lt;p&gt;Usage of loops could also be made more Pythonic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i,archive in enumerate(archiveList):
  if i == len(archiveList) - 1:
    break
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;could be actually:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for archive in itertools.islice(archiveList, len(archiveList) - 1):
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That reduce the code size and makes it easier to read through the code.&lt;/p&gt;
&lt;h2&gt;Wrong abstraction level&lt;/h2&gt;
&lt;p&gt;Also, one thing that I noticed in &lt;em&gt;whisper&lt;/em&gt;, is that it abstracts its features at the wrong level.&lt;/p&gt;
&lt;p&gt;Take the &lt;code&gt;create()&lt;/code&gt; function, it&apos;s pretty obvious:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def create(path,archiveList,xFilesFactor=None,aggregationMethod=None,sparse=False,useFallocate=False):
  # Set default params
  if xFilesFactor is None:
    xFilesFactor = 0.5
  if aggregationMethod is None:
    aggregationMethod = &apos;average&apos;

  #Validate archive configurations...
  validateArchiveList(archiveList)

  #Looks good, now we create the file and write the header
  if os.path.exists(path):
    raise InvalidConfiguration(&quot;File %s already exists!&quot; % path)
  fh = None
  try:
    fh = open(path,&apos;wb&apos;)
    if LOCK:
      fcntl.flock( fh.fileno(), fcntl.LOCK_EX )

    aggregationType = struct.pack( longFormat, aggregationMethodToType.get(aggregationMethod, 1) )
    oldest = max([secondsPerPoint * points for secondsPerPoint,points in archiveList])
    maxRetention = struct.pack( longFormat, oldest )
    xFilesFactor = struct.pack( floatFormat, float(xFilesFactor) )
    archiveCount = struct.pack(longFormat, len(archiveList))
    packedMetadata = aggregationType + maxRetention + xFilesFactor + archiveCount
    fh.write(packedMetadata)
    headerSize = metadataSize + (archiveInfoSize * len(archiveList))
    archiveOffsetPointer = headerSize

    for secondsPerPoint,points in archiveList:
      archiveInfo = struct.pack(archiveInfoFormat, archiveOffsetPointer, secondsPerPoint, points)
      fh.write(archiveInfo)
      archiveOffsetPointer += (points * pointSize)

    #If configured to use fallocate and capable of fallocate use that, else
    #attempt sparse if configure or zero pre-allocate if sparse isn&apos;t configured.
    if CAN_FALLOCATE and useFallocate:
      remaining = archiveOffsetPointer - headerSize
      fallocate(fh, headerSize, remaining)
    elif sparse:
      fh.seek(archiveOffsetPointer - 1)
      fh.write(&apos;\x00&apos;)
    else:
      remaining = archiveOffsetPointer - headerSize
      chunksize = 16384
      zeroes = &apos;\x00&apos; * chunksize
      while remaining &amp;gt; chunksize:
        fh.write(zeroes)
        remaining -= chunksize
      fh.write(zeroes[:remaining])

    if AUTOFLUSH:
      fh.flush()
      os.fsync(fh.fileno())
  finally:
    if fh:
      fh.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function is doing &lt;strong&gt;everything&lt;/strong&gt;: checking if the file doesn&apos;t exist already, opening it, building the structured data, writing this, building more structure, then writing that, etc.&lt;/p&gt;
&lt;p&gt;That means that the caller has to give a file path, even if it just wants a &lt;em&gt;whipser&lt;/em&gt; data structure to store itself elsewhere. &lt;code&gt;StringIO()&lt;/code&gt; could be used to fake a file handler, but it will fail if the call to &lt;code&gt;fcntl.flock()&lt;/code&gt; is not disabled – and it is inefficient anyway.&lt;/p&gt;
&lt;p&gt;There&apos;s a lot of other functions in the code, such as for example &lt;code&gt;setAggregationMethod()&lt;/code&gt;, that mixes the handling of the files – even doing things like &lt;code&gt;os.fsync()&lt;/code&gt; – while manipulating structured data. This is definitely not a good design, especially for a library, as it turns out reusing the function in different context is near impossible.&lt;/p&gt;
&lt;h2&gt;Race conditions&lt;/h2&gt;
&lt;p&gt;There are race conditions, for example in &lt;code&gt;create()&lt;/code&gt; (see added comment):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if os.path.exists(path):
  raise InvalidConfiguration(&quot;File %s already exists!&quot; % path)
fh = None
try:
  # TOO LATE I ALREADY CREATED THE FILE IN ANOTHER PROCESS YOU ARE GOING TO
  # FAIL WITHOUT GIVING ANY USEFUL INFORMATION TO THE CALLER :-(
  fh = open(path,&apos;wb&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That code should be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try:
  fh = os.fdopen(os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL), &apos;wb&apos;)
except OSError as e:
  if e.errno == errno.EEXIST:
    raise InvalidConfiguration(&quot;File %s already exists!&quot; % path)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to avoid any race condition.&lt;/p&gt;
&lt;h2&gt;Unwanted optimization&lt;/h2&gt;
&lt;p&gt;We saw earlier the &lt;code&gt;fetch()&lt;/code&gt; function that is barely useful, so let&apos;s take a look at the &lt;code&gt;file_fetch()&lt;/code&gt; function that it&apos;s calling.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def file_fetch(fh, fromTime, untilTime, now = None):
  header = __readHeader(fh)
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing the function does is to read the header from the file handler.&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at that function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def __readHeader(fh):
  info = __headerCache.get(fh.name)
  if info:
    return info

  originalOffset = fh.tell()
  fh.seek(0)
  packedMetadata = fh.read(metadataSize)

  try:
    (aggregationType,maxRetention,xff,archiveCount) = struct.unpack(metadataFormat,packedMetadata)
  except:
    raise CorruptWhisperFile(&quot;Unable to read header&quot;, fh.name)
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing the function does is to look into a cache. Why is there a cache?&lt;/p&gt;
&lt;p&gt;It actually caches the header based with an index based on the file path (&lt;code&gt;fh.name&lt;/code&gt;). Except that if one for example decide not to use file and cheat using &lt;code&gt;StringIO&lt;/code&gt;, then it does not have any name attribute. So this code path will raise an &lt;code&gt;AttributeError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One has to set a fake name manually on the &lt;code&gt;StringIO&lt;/code&gt; instance, and it must be unique so nobody messes with the cache&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import StringIO

packedMetadata = &amp;lt;some source&amp;gt;
fh = StringIO.StringIO(packedMetadata)
fh.name = &quot;myfakename&quot;
header = __readHeader(fh)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cache may actually be useful when accessing files, but it&apos;s definitely useless when not using files. But it&apos;s not necessarily true that the complexity (even if small) that the cache adds is worth it. I doubt most of &lt;em&gt;whisper&lt;/em&gt; based tools are long run processes, so the cache that is really used when accessing the files is the one handled by the operating system kernel, and this one is going to be much more efficient anyway, and shared between processed. There&apos;s also no expiry of that cache, which could end up of tons of memory used and wasted.&lt;/p&gt;
&lt;h2&gt;Docstrings&lt;/h2&gt;
&lt;p&gt;None of the docstrings are written in a a parsable syntax like &lt;a href=&quot;http://sphinx-doc.org/&quot;&gt;Sphinx&lt;/a&gt;. This means you cannot generate any documentation in a nice format that a developer using the library could read easily.&lt;/p&gt;
&lt;p&gt;The documentation is also not up to date:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def fetch(path,fromTime,untilTime=None,now=None):
  &quot;&quot;&quot;fetch(path,fromTime,untilTime=None)
[...]
&quot;&quot;&quot;

def create(path,archiveList,xFilesFactor=None,aggregationMethod=None,sparse=False,useFallocate=False):
  &quot;&quot;&quot;create(path,archiveList,xFilesFactor=0.5,aggregationMethod=&apos;average&apos;)
[...]
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is something that could be avoided if a proper format was picked to write the docstring. A tool cool be used to be noticed when there&apos;s a diversion between the actual function signature and the documented one, like missing an argument.&lt;/p&gt;
&lt;h2&gt;Duplicated code&lt;/h2&gt;
&lt;p&gt;Last but not least, there&apos;s a lot of code that is duplicated around in the scripts provided by &lt;em&gt;whisper&lt;/em&gt; in its &lt;code&gt;bin&lt;/code&gt; directory. Theses scripts should be very lightweight and be using the &lt;code&gt;console_scripts&lt;/code&gt; facility of &lt;em&gt;setuptools&lt;/em&gt;, but they actually contains a lot of (untested) code. Furthermore, some of that code is partially duplicated from the &lt;code&gt;whisper.py&lt;/code&gt; library which is against &lt;a href=&quot;http://en.wikipedia.org/wiki/Don&apos;t_repeat_yourself&quot;&gt;DRY&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There are a few more things that made me stop considering &lt;em&gt;whisper&lt;/em&gt;, but these are part of the &lt;em&gt;whisper&lt;/em&gt; features, not necessarily code quality. One can also point out that the code is very condensed and hard to read, and that&apos;s a more general problem about how it is organized and abstracted.&lt;/p&gt;
&lt;p&gt;A lot of these defects are actually points that made me start writing &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt; a year ago.&lt;br /&gt;
Running into this kind of code makes me think it was a really good idea to write a book on advice to write better Python code!&lt;/p&gt;
</content:encoded><category>python</category><category>monitoring</category></item><item><title>First release of PyMuninCli</title><link>https://julien.danjou.info/blog/pymunincli-0-1/</link><guid isPermaLink="true">https://julien.danjou.info/blog/pymunincli-0-1/</guid><description>Today I release a Python client library to query Munin servers.</description><pubDate>Tue, 17 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today I release a &lt;a href=&quot;http://python.org&quot;&gt;Python&lt;/a&gt; client library to query &lt;a href=&quot;http://munin-monitoring.org/&quot;&gt;Munin&lt;/a&gt; servers.&lt;/p&gt;
&lt;p&gt;I wrote it as part of some experiments I did a few weeks ago. I discovered there was no client library to query a Munin server. There&apos;s &lt;a href=&quot;http://aouyar.github.com/PyMunin/&quot;&gt;PyMunin&lt;/a&gt; or &lt;a href=&quot;http://samuelks.com/python-munin/&quot;&gt;python-munin&lt;/a&gt; which help developing Munin plugins, but nothing to access the &lt;em&gt;munin-node&lt;/em&gt; and retrieve its data.&lt;/p&gt;
&lt;p&gt;So I decided to write a quick and simple one, and it&apos;s released under the name of &lt;a href=&quot;https://github.com/jd/pymunincli&quot;&gt;PyMuninCli&lt;/a&gt;, providing the &lt;em&gt;munin.client&lt;/em&gt; Python module.&lt;/p&gt;
</content:encoded><category>python</category><category>monitoring</category></item></channel></rss>