This file is indexed.

/usr/share/pyshared/archipelagenthypervisorhealth/archipelStatsCollector.py is in archipel-agent-hypervisor-health 0.6.0-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# -*- coding: utf-8 -*-
#
# archipelStatsCollector.py
#
# Copyright (C) 2010 Antoine Mercadal <antoine.mercadal@inframonde.eu>
# Copyright, 2011 - Franck Villaume <franck.villaume@trivialdev.com>
# This file is part of ArchipelProject
# http://archipelproject.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import datetime
import sqlite3
import subprocess
import time
import json
from threading import Thread

from archipelcore.utils import log


class TNThreadedHealthCollector (Thread):
    """
    This class collects hypervisor stats regularly.
    """

    def __init__(self, database_file, collection_interval, max_rows_before_purge, max_cached_rows):
        """
        The contructor of the class.
        @type database_file: string
        @param database_file: the path of the database
        @type collection_interval: integer
        @param collection_interval: the intervale between two collection
        @type max_rows_before_purge: integer
        @param max_rows_before_purge: max number of rows that can be stored in database
        @type max_cached_rows: integer
        @param max_cached_rows: max number of rows that are cached into memory
        """
        self.database_file          = database_file
        self.collection_interval    = collection_interval
        self.max_rows_before_purge  = max_rows_before_purge
        self.max_cached_rows        = max_cached_rows
        self.stats_CPU              = []
        self.stats_memory           = []
        self.stats_load             = []
        self.stats_network          = []
        self.current_record         = {}
        self.memoryPageSize         = int(subprocess.Popen(["getconf", "PAGESIZE"], stdout=subprocess.PIPE).communicate()[0])
        uname = subprocess.Popen(["uname", "-rsmo"], stdout=subprocess.PIPE).communicate()[0].split()
        self.uname_stats = {"krelease": uname[0], "kname": uname[1], "machine": uname[2], "os": uname[3]}
        self.database_query_connection = sqlite3.connect(self.database_file)
        self.cursor = self.database_query_connection.cursor()
        self.cursor.execute("create table if not exists cpu (collection_date date, idle int)")
        self.cursor.execute("create table if not exists memory (collection_date date, free integer, used integer, total integer, swapped integer, shared integer)")
        self.cursor.execute("create table if not exists load (collection_date date, one float, five float, fifteen float)")
        self.cursor.execute("create table if not exists network (collection_date date, records text)")
        self.database_query_connection.commit()
        log.info("Database ready.")
        self.recover_stored_stats()
        self.cursor.close()
        Thread.__init__(self)

    def recover_stored_stats(self):
        """
        Recover info from database.
        """
        log.info("Recovering stored statistics. It may take a while...")
        self.cursor.execute("select * from cpu order by collection_date desc limit %d" % self.max_cached_rows)
        for values in self.cursor:
            date, idle = values
            self.stats_CPU.append({"date": date, "id": idle})
        self.stats_CPU.reverse()
        self.cursor.execute("select * from memory order by collection_date desc limit %d" % self.max_cached_rows)
        for values in self.cursor:
            date, free, used, total, swapped, shared = values
            self.stats_memory.append({"date": date, "free": free, "used": used, "total": total, "swapped": swapped, "shared": shared})
        self.stats_memory.reverse()
        self.cursor.execute("select * from load order by collection_date desc limit %d" % self.max_cached_rows)
        for values in self.cursor:
            date, one, five, fifteen = values
            self.stats_load.append({"date": date, "one": one, "five": five, "fifteen": fifteen})
        self.stats_load.reverse()
        self.cursor.execute("select * from network order by collection_date desc limit %d" % self.max_cached_rows)
        for values in self.cursor:
            date, records = values
            self.stats_network.append({"date": date, "records": records})
        self.stats_network.reverse()
        log.info("Statistics recovered.")

    def get_collected_stats(self, limit=1):
        """
        This method returns the current L{TNArchipelVirtualMachine} instance.
        @type limit: integer
        @param limit: the max number of row to get
        @rtype: TNArchipelVirtualMachine
        @return: the L{TNArchipelVirtualMachine} instance
        """
        log.debug("STATCOLLECTOR: Retrieving last "+ str(limit) + " recorded stats data for sending.")
        try:
            uptime = self.get_uptime()
            uptime_stats    = {"up": "%dd %dh" % (uptime[0], uptime[1])} #TODO: it's obvious it would be better to not do this
        except Exception as ex:
            raise Exception("Unable to get uptime.", ex)
        try:
            acpu = self.stats_CPU[-limit:]
        except Exception as ex:
            raise Exception("Unable to get CPU stats.", ex)
        try:
            amem = self.stats_memory[-limit:]
        except Exception as ex:
            raise Exception("Unable to get memory.", ex)
        try:
            anetwork = self.stats_network[-limit:]
        except Exception as ex:
            raise Exception("Unable to get networks.", ex)
        try:
            adisk = sorted(self.get_disk_stats(), cmp=lambda x, y: cmp(x["mount"], y["mount"]))
            totalDisk = self.get_disk_total()
        except Exception as ex:
            raise Exception("Unable to get disks information.", ex)
        try:
            aload = self.stats_load[-limit:]
        except Exception as ex:
            raise Exception("Unable to get disks information.", ex)
        if limit > 1:
            acpu.reverse()
            amem.reverse()
            aload.reverse()
            anetwork.reverse()
        return {"cpu": acpu, "memory": amem, "disk": adisk, "totaldisk": totalDisk,
                "load": aload, "uptime": uptime_stats, "uname": self.uname_stats, "network": anetwork}

    def get_uptime(self):
        """
        Get the uptime from /proc/uptime.
        code taken from http://thesmithfam.org/blog/2005/11/19/python-uptime-script/
        @rtype: tupple
        @return: days, hours, minutes, seconds
        """
        f = open('/proc/uptime')
        contents = f.read().split()
        f.close()
        total_seconds = float(contents[0])
        MINUTE = 60
        HOUR = MINUTE * 60
        DAY = HOUR * 24
        days = int(total_seconds / DAY)
        hours = int((total_seconds % DAY) / HOUR)
        minutes = int((total_seconds % HOUR) / MINUTE)
        seconds = int(total_seconds % MINUTE)
        return (days, hours, minutes, seconds)

    def get_memory_stats(self):
        """
        Get memory stats.
        @rtype: dict
        @return: dictionnary containing the informations
        """
        file_meminfo = open('/proc/meminfo')
        meminfo = file_meminfo.read()
        file_meminfo.close()
        file_pages_sharing = open("/sys/kernel/mm/ksm/pages_sharing")
        pagessharing = file_pages_sharing.read()
        file_pages_sharing.close()
        memshared = int(pagessharing) * self.memoryPageSize / 1024;
        meminfolines = meminfo.split("\n")
        memTotal = int(meminfolines[0].split()[1])
        memFree = int(meminfolines[1].split()[1]) + int(meminfolines[2].split()[1]) + int(meminfolines[3].split()[1])
        swapped = int(meminfolines[4].split()[1])
        memUsed = memTotal - memFree
        return {"date": datetime.datetime.now(), "free": memFree, "used": memUsed, "total": memTotal, "swapped": swapped, "shared": memshared}

    def get_cpu_stats(self):
        """
        Get CPU stats.
        @rtype: dict
        @return: dictionnary containing the informations
        """
        dt = self.deltaTime(1)
        cpuPct = (dt[len(dt) - 1] * 100.00 / sum(dt))
        return {"date": datetime.datetime.now(), "id": cpuPct}

    def get_load_stats(self):
        """
        Get load stats.
        @rtype: dict
        @return: dictionnary containing the informations
        """
        f = open('/proc/loadavg')
        contents = f.read().split()
        f.close()
        load1min = float(contents[0])
        load5min = float(contents[1])
        load15min = float(contents[2])
        return {"date": datetime.datetime.now(), "one": load1min, "five": load5min, "fifteen": load15min}

    def get_disk_stats(self):
        """
        Get drive usage stats.
        @rtype: dict
        @return: dictionnary containing the informations
        """
        output  = subprocess.Popen(["df", "-P", "-x", "devfs", "-x", "devtmpfs", "-x", "tmpfs"], stdout=subprocess.PIPE).communicate()[0]
        listed  = []
        ret     = []
        out     = output.split("\n")[1:-1]
        for l in out:
            cell = l.split()
            if cell[5] in listed:
                continue
            ret.append({"partition": cell[0], "blocks": cell[1], "used": int(cell[2]) * 1024, "available": int(cell[3]) * 1024, "capacity": cell[4], "mount": cell[5]})
            listed.append(cell[5])
        return ret

    def get_disk_total(self):
        """
        Get total size of drive used stats.
        @rtype: dict
        @return: dictionnary containing the informations
        """
        out = subprocess.Popen(["df", "--total", "-P", "-x", "devfs", "-x", "devtmpfs", "-x", "tmpfs"], stdout=subprocess.PIPE).communicate()[0].split("\n")
        for line in out:
            line = line.split()
            if line[0] == "total":
                disk_total = line
                break
        return {"used": disk_total[2], "available": disk_total[3], "capacity": disk_total[4]}

    def get_network_stats(self):
        """
        Get network stats.
        @rtype: dict
        @return: dictionnary containing the informations
        """
        f = open('/proc/net/dev')
        contents = f.read().split('\n')[2:-1]
        f.close()
        records = {}
        ret = {}
        for line in contents:
            dev = line.split(":")[0].replace(" ", "")
            if dev == "lo":
                continue
            if dev.startswith("vnet"):
                continue
            info = line.split(":")[1].split()
            rx = int(info[0])
            tx = int(info[8])
            records[dev] = {"rx": rx, "tx": tx}
            if self.current_record and dev in self.current_record:
                delta_usage = (rx - self.current_record[dev]["rx"]) + (tx - self.current_record[dev]["tx"])
            else:
                delta_usage = 0;
            ret[dev] = delta_usage
        self.current_record = records;
        return {"date": datetime.datetime.now(), "records": json.dumps(ret)}

    def getTimeList(self):
        """
        ignore
        """
        statFile = file('/proc/stat')
        timeList = statFile.readline().split(" ")[2:6]
        statFile.close()
        for i in range(len(timeList)):
            timeList[i] = int(timeList[i])
        return timeList

    def deltaTime(self, interval):
        """
        ignore
        """
        x = self.getTimeList()
        time.sleep(interval)
        y = self.getTimeList()
        for i in range(len(x)):
            y[i] -= x[i]
        return y

    def run(self):
        """
        Overrides super class method. do the L{TNArchipelVirtualMachine} main loop.
        """
        self.database_thread_connection = sqlite3.connect(self.database_file)
        self.database_thread_cursor = self.database_thread_connection.cursor()
        while(1):
            try:
                self.stats_CPU.append(self.get_cpu_stats())
                self.stats_memory.append(self.get_memory_stats())
                self.stats_load.append(self.get_load_stats())
                self.stats_network.append(self.get_network_stats())

                if len(self.stats_CPU) >= self.max_cached_rows:
                    middle = (self.max_cached_rows - 1) / 2

                    self.database_thread_cursor.executemany("insert into memory values(:date, :free, :used, :total, :swapped, :shared)", self.stats_memory[0:middle])
                    self.database_thread_cursor.executemany("insert into cpu values(:date, :id)", self.stats_CPU[0:middle])
                    self.database_thread_cursor.executemany("insert into load values(:date, :one , :five, :fifteen)", self.stats_load[0:middle])
                    self.database_thread_cursor.executemany("insert into network values(:date, :records)", self.stats_network[0:middle])

                    log.info("Stats saved in database file.")
                    nrRow = int(self.database_thread_cursor.execute("select count(*) from cpu").fetchone()[0]);
                    if  nrRow > self.max_rows_before_purge * 1.5:
                        self.database_thread_cursor.execute("DELETE FROM cpu WHERE collection_date IN (SELECT collection_date FROM cpu ORDER BY collection_date ASC LIMIT " + str(nrRow - self.max_rows_before_purge) + ")")
                        self.database_thread_cursor.execute("DELETE FROM memory WHERE collection_date IN (SELECT collection_date FROM memory ORDER BY collection_date ASC LIMIT " + str(nrRow - self.max_rows_before_purge) + ")")
                        self.database_thread_cursor.execute("DELETE FROM load WHERE collection_date IN (SELECT collection_date FROM load ORDER BY collection_date ASC LIMIT " + str(nrRow - self.max_rows_before_purge) + ")")
                        self.database_thread_cursor.execute("DELETE FROM network WHERE collection_date IN (SELECT collection_date FROM network ORDER BY collection_date ASC LIMIT " + str(nrRow - self.max_rows_before_purge) + ")")
                        log.debug("Old stored stats have been purged from database.")

                    del self.stats_CPU[0:middle]
                    del self.stats_memory[0:middle]
                    del self.stats_load[0:middle]
                    del self.stats_network[0:middle]
                    log.info("Cached stats have been purged from memory.")

                    self.database_thread_connection.commit()

                time.sleep(self.collection_interval)
            except Exception as ex:
                log.error("Stat collection fails. Exception %s" % str(ex))