apps.plugin now checks fds for changes, with adaptive caching (#4243)
* apps.plugin now checks fds for changes, with adaptive caching * fixed typo * freebsd fix * freebsd fix 2 * freebsd fix 3 * always call init_pid_fds() to allow custom initialization code
Costa Tsaousis committed
Sep 22, 2018 at 01:51 UTC
04ee49192be12c3167b03b91dfda1ca832ec7501
1 file changed
+192
-56
src/apps_plugin.c
+192
-56
@@ -93,6 +93,7 @@ static int
93
enable_file_charts = 0,
94
#else
95
enable_file_charts = 1,
96
+ max_fds_cache_seconds = 60,
97
#endif
98
enable_users_charts = 1,
99
enable_groups_charts = 1,
@@ -119,6 +120,9 @@ static size_t
120
global_iterations_counter = 1,
121
calls_counter = 0,
122
file_counter = 0,
123
+ filenames_allocated_counter = 0,
124
+ inodes_changed_counter = 0,
125
+ links_changed_counter = 0,
126
targets_assignment_counter = 0;
127
128
@@ -247,6 +251,18 @@ size_t
251
// structure to store data for each process running
252
// see: man proc for the description of the fields
253
254
+struct pid_fd {
255
+ int fd;
256
+
257
+#ifndef __FreeBSD__
258
+ ino_t inode;
259
+ char *filename;
260
+ uint32_t link_hash;
261
+ size_t cache_iterations_counter;
262
+ size_t cache_iterations_reset;
263
+#endif
264
+};
265
+
266
struct pid_stat {
267
int32_t pid;
268
char comm[MAX_COMPARE_NAME + 1];
@@ -341,8 +357,8 @@ struct pid_stat {
357
kernel_uint_t io_storage_bytes_written;
358
// kernel_uint_t io_cancelled_write_bytes;
359
344
- int *fds; // array of fds it uses
345
- int fds_size; // the size of the fds array
360
+ struct pid_fd *fds; // array of fds it uses
361
+ size_t fds_size; // the size of the fds array
362
363
int children_count; // number of processes directly referencing this
364
unsigned char keep:1; // 1 when we need to keep this process in memory even after it exited
@@ -684,14 +700,16 @@ static int read_apps_groups_conf(const char *file)
700
701
// ----------------------------------------------------------------------------
702
// struct pid_stat management
703
+static inline void init_pid_fds(struct pid_stat *p, size_t first, size_t size);
704
705
static inline struct pid_stat *get_pid_entry(pid_t pid) {
706
if(unlikely(all_pids[pid]))
707
return all_pids[pid];
708
709
struct pid_stat *p = callocz(sizeof(struct pid_stat), 1);
693
- p->fds = callocz(sizeof(int), MAX_SPARE_FDS);
710
+ p->fds = mallocz(sizeof(struct pid_fd) * MAX_SPARE_FDS);
711
p->fds_size = MAX_SPARE_FDS;
712
+ init_pid_fds(p, 0, p->fds_size);
713
714
if(likely(root_of_pids))
715
root_of_pids->prev = p;
@@ -723,7 +741,17 @@ static inline void del_pid_entry(pid_t pid) {
741
if(p->next) p->next->prev = p->prev;
742
if(p->prev) p->prev->next = p->next;
743
744
+ // free the filename
745
+#ifndef __FreeBSD__
746
+ {
747
+ size_t i;
748
+ for(i = 0; i < p->fds_size; i++)
749
+ if(p->fds[i].filename)
750
+ freez(p->fds[i].filename);
751
+ }
752
+#endif
753
freez(p->fds);
754
+
755
freez(p->fds_dirname);
756
freez(p->stat_filename);
757
freez(p->status_filename);
@@ -1517,9 +1545,9 @@ static inline int file_descriptor_set_on_empty_slot(const char *name, uint32_t h
1545
return c;
1546
}
1547
1520
-static inline int file_descriptor_find_or_add(const char *name)
1521
-{
1522
- uint32_t hash = simple_hash(name);
1548
+static inline int file_descriptor_find_or_add(const char *name, uint32_t hash) {
1549
+ if(unlikely(!hash))
1550
+ hash = simple_hash(name);
1551
1552
debug_log("adding or finding name '%s' with hash %u", name, hash);
1553
@@ -1559,30 +1587,52 @@ static inline int file_descriptor_find_or_add(const char *name)
1587
return file_descriptor_set_on_empty_slot(name, hash, type);
1588
}
1589
1590
+static inline void clear_pid_fd(struct pid_fd *pfd) {
1591
+ pfd->fd = 0;
1592
+
1593
+ #ifndef __FreeBSD__
1594
+ pfd->link_hash = 0;
1595
+ pfd->inode = 0;
1596
+ pfd->cache_iterations_counter = 0;
1597
+ pfd->cache_iterations_reset = 0;
1598
+#endif
1599
+}
1600
+
1601
static inline void make_all_pid_fds_negative(struct pid_stat *p) {
1563
- int *fd = p->fds, *end = &p->fds[p->fds_size];
1564
- while(fd < end) {
1565
- *fd = -(*fd);
1566
- fd++;
1602
+ struct pid_fd *pfd = p->fds, *pfdend = &p->fds[p->fds_size];
1603
+ while(pfd < pfdend) {
1604
+ pfd->fd = -(pfd->fd);
1605
+ pfd++;
1606
}
1607
}
1608
1609
static inline void cleanup_negative_pid_fds(struct pid_stat *p) {
1571
- int *fd = p->fds, *fdend = &p->fds[p->fds_size];
1610
+ struct pid_fd *pfd = p->fds, *pfdend = &p->fds[p->fds_size];
1611
1573
- while(fd < fdend) {
1574
- if(unlikely(*fd < 0)) {
1575
- file_descriptor_not_used(-(*fd));
1576
- *fd++ = 0;
1612
+ while(pfd < pfdend) {
1613
+ int fd = pfd->fd;
1614
+
1615
+ if(unlikely(fd < 0)) {
1616
+ file_descriptor_not_used(-(fd));
1617
+ clear_pid_fd(pfd);
1618
}
1578
- else
1579
- fd++;
1619
+
1620
+ pfd++;
1621
}
1622
}
1623
1583
-static inline void zero_pid_fds(struct pid_stat *p, int first, int size) {
1584
- int *fd = &p->fds[first], *end = &p->fds[first + size];
1585
- while(fd < end) *fd++ = 0;
1624
+static inline void init_pid_fds(struct pid_stat *p, size_t first, size_t size) {
1625
+ struct pid_fd *pfd = &p->fds[first], *pfdend = &p->fds[first + size];
1626
+ size_t i = first;
1627
+
1628
+ while(pfd < pfdend) {
1629
+#ifndef __FreeBSD__
1630
+ pfd->filename = NULL;
1631
+#endif
1632
+ clear_pid_fd(pfd);
1633
+ pfd++;
1634
+ i++;
1635
+ }
1636
}
1637
1638
static inline int read_pid_file_descriptors(struct pid_stat *p, void *ptr) {
@@ -1639,14 +1689,14 @@ static inline int read_pid_file_descriptors(struct pid_stat *p, void *ptr) {
1689
1690
debug_log("extending fd memory slots for %s from %d to %d", p->comm, p->fds_size, fdid + MAX_SPARE_FDS);
1691
1642
- p->fds = reallocz(p->fds, (fdid + MAX_SPARE_FDS) * sizeof(int));
1692
+ p->fds = reallocz(p->fds, (fdid + MAX_SPARE_FDS) * sizeof(struct pid_fd));
1693
1694
// and initialize it
1645
- zero_pid_fds(p, p->fds_size, (fdid + MAX_SPARE_FDS) - p->fds_size);
1695
+ init_pid_fds(p, p->fds_size, (fdid + MAX_SPARE_FDS) - p->fds_size);
1696
p->fds_size = fdid + MAX_SPARE_FDS;
1697
}
1698
1649
- if (unlikely(p->fds[fdid] == 0)) {
1699
+ if (unlikely(p->fds[fdid].fd == 0)) {
1700
// we don't know this fd, get it
1701
1702
switch (fds->kf_type) {
@@ -1702,15 +1752,14 @@ static inline int read_pid_file_descriptors(struct pid_stat *p, void *ptr) {
1752
1753
// if another process already has this, we will get
1754
// the same id
1705
- p->fds[fdid] = file_descriptor_find_or_add(fdsname);
1755
+ p->fds[fdid].fd = file_descriptor_find_or_add(fdsname, 0);
1756
}
1757
1758
// else make it positive again, we need it
1709
- // of course, the actual file may have changed, but we don't care so much
1710
- // FIXME: we could compare the inode as returned by readdir dirent structure
1759
+ // of course, the actual file may have changed
1760
1761
else
1713
- p->fds[fdid] = -p->fds[fdid];
1762
+ p->fds[fdid].fd = -p->fds[fdid].fd;
1763
1764
bfdsbuf += fds->kf_structsize;
1765
}
@@ -1725,7 +1774,6 @@ static inline int read_pid_file_descriptors(struct pid_stat *p, void *ptr) {
1774
if(unlikely(!fds)) return 0;
1775
1776
struct dirent *de;
1728
- char fdname[FILENAME_MAX + 1];
1777
char linkname[FILENAME_MAX + 1];
1778
1779
// we make all pid fds negative, so that
@@ -1744,7 +1792,7 @@ static inline int read_pid_file_descriptors(struct pid_stat *p, void *ptr) {
1792
if(unlikely(fdid < 0)) continue;
1793
1794
// check if the fds array is small
1747
- if(unlikely(fdid >= p->fds_size)) {
1795
+ if(unlikely((size_t)fdid >= p->fds_size)) {
1796
// it is small, extend it
1797
1798
debug_log("extending fd memory slots for %s from %d to %d"
@@ -1753,41 +1801,94 @@ static inline int read_pid_file_descriptors(struct pid_stat *p, void *ptr) {
1801
, fdid + MAX_SPARE_FDS
1802
);
1803
1756
- p->fds = reallocz(p->fds, (fdid + MAX_SPARE_FDS) * sizeof(int));
1804
+ p->fds = reallocz(p->fds, (fdid + MAX_SPARE_FDS) * sizeof(struct pid_fd));
1805
1806
// and initialize it
1759
- zero_pid_fds(p, p->fds_size, (fdid + MAX_SPARE_FDS) - p->fds_size);
1760
- p->fds_size = fdid + MAX_SPARE_FDS;
1807
+ init_pid_fds(p, p->fds_size, (fdid + MAX_SPARE_FDS) - p->fds_size);
1808
+ p->fds_size = (size_t)fdid + MAX_SPARE_FDS;
1809
}
1810
1763
- if(unlikely(p->fds[fdid] == 0)) {
1764
- // we don't know this fd, get it
1811
+ if(unlikely(p->fds[fdid].fd != 0 && de->d_ino != p->fds[fdid].inode)) {
1812
+ // inodes do not match, clear the previous entry
1813
+ inodes_changed_counter++;
1814
+ file_descriptor_not_used(p->fds[fdid].fd);
1815
+ clear_pid_fd(&p->fds[fdid]);
1816
+ }
1817
1766
- sprintf(fdname, "%s/proc/%d/fd/%s", netdata_configured_host_prefix, p->pid, de->d_name);
1767
- ssize_t l = readlink(fdname, linkname, FILENAME_MAX);
1768
- if(unlikely(l == -1)) {
1769
- if(debug_enabled || (p->target && p->target->debug_enabled))
1770
- error("Cannot read link %s", fdname);
1818
+ if(p->fds[fdid].fd < 0 && p->fds[fdid].cache_iterations_counter > 0) {
1819
+ p->fds[fdid].fd = -p->fds[fdid].fd;
1820
+ p->fds[fdid].cache_iterations_counter--;
1821
+ continue;
1822
+ }
1823
1772
- continue;
1824
+ if(unlikely(!p->fds[fdid].filename)) {
1825
+ filenames_allocated_counter++;
1826
+ char fdname[FILENAME_MAX + 1];
1827
+ snprintfz(fdname, FILENAME_MAX, "%s/proc/%d/fd/%s", netdata_configured_host_prefix, p->pid, de->d_name);
1828
+ p->fds[fdid].filename = strdupz(fdname);
1829
+ }
1830
+
1831
+ file_counter++;
1832
+ ssize_t l = readlink(p->fds[fdid].filename, linkname, FILENAME_MAX);
1833
+ if(unlikely(l == -1)) {
1834
+ // cannot read the link
1835
+
1836
+ if(debug_enabled || (p->target && p->target->debug_enabled))
1837
+ error("Cannot read link %s", p->fds[fdid].filename);
1838
+
1839
+ if(unlikely(p->fds[fdid].fd)) {
1840
+ file_descriptor_not_used(p->fds[fdid].fd);
1841
+ clear_pid_fd(&p->fds[fdid]);
1842
}
1774
- else
1775
- linkname[l] = '\0';
1843
1777
- file_counter++;
1844
+ continue;
1845
+ }
1846
+ else
1847
+ linkname[l] = '\0';
1848
+
1849
+ uint32_t link_hash = simple_hash(linkname);
1850
+
1851
+ if(unlikely(p->fds[fdid].fd && p->fds[fdid].link_hash != link_hash)) {
1852
+ // the link changed
1853
+ links_changed_counter++;
1854
+ file_descriptor_not_used(p->fds[fdid].fd);
1855
+ clear_pid_fd(&p->fds[fdid]);
1856
+ }
1857
+
1858
+ if(unlikely(p->fds[fdid].fd == 0)) {
1859
+ // we don't know this fd, get it
1860
1861
// if another process already has this, we will get
1862
// the same id
1781
- p->fds[fdid] = file_descriptor_find_or_add(linkname);
1863
+ p->fds[fdid].fd = file_descriptor_find_or_add(linkname, link_hash);
1864
+ p->fds[fdid].inode = de->d_ino;
1865
+ p->fds[fdid].link_hash = link_hash;
1866
}
1783
-
1867
+ else {
1868
// else make it positive again, we need it
1785
- // of course, the actual file may have changed, but we don't care so much
1786
- // FIXME: we could compare the inode as returned by readdir dirent structure
1787
- // UPDATE: no we cannot use inodes - under /proc inodes don't change when the link is changed
1869
+ p->fds[fdid].fd = -p->fds[fdid].fd;
1870
+ }
1871
1789
- else
1790
- p->fds[fdid] = -p->fds[fdid];
1872
+ // caching control
1873
+ // without this we read all the files on every iteration
1874
+ if(max_fds_cache_seconds > 0) {
1875
+ size_t spread = ((size_t)max_fds_cache_seconds > 10) ? 10 : (size_t)max_fds_cache_seconds;
1876
+
1877
+ // cache it for a few iterations
1878
+ size_t max = ((size_t) max_fds_cache_seconds + (fdid % spread)) / (size_t) update_every;
1879
+ p->fds[fdid].cache_iterations_reset++;
1880
+
1881
+ if(unlikely(p->fds[fdid].cache_iterations_reset % spread == (size_t) fdid % spread))
1882
+ p->fds[fdid].cache_iterations_reset++;
1883
+
1884
+ if(unlikely((fdid <= 2 && p->fds[fdid].cache_iterations_reset > 5) ||
1885
+ p->fds[fdid].cache_iterations_reset > max)) {
1886
+ // for stdin, stdout, stderr (fdid <= 2) we have checked a few times, or if it goes above the max, goto max
1887
+ p->fds[fdid].cache_iterations_reset = max;
1888
+ }
1889
+
1890
+ p->fds[fdid].cache_iterations_counter = p->fds[fdid].cache_iterations_reset;
1891
+ }
1892
}
1893
1894
closedir(fds);
@@ -2306,7 +2407,7 @@ static int collect_data_for_all_processes(void) {
2407
// check: update_apps_groups_statistics()
2408
2409
static void cleanup_exited_pids(void) {
2309
- int c;
2410
+ size_t c;
2411
struct pid_stat *p = NULL;
2412
2413
for(p = root_of_pids; p ;) {
@@ -2315,9 +2416,9 @@ static void cleanup_exited_pids(void) {
2416
debug_log(" > CLEANUP cannot keep exited process %d (%s) anymore - removing it.", p->pid, p->comm);
2417
2418
for(c = 0; c < p->fds_size; c++)
2318
- if(p->fds[c] > 0) {
2319
- file_descriptor_not_used(p->fds[c]);
2320
- p->fds[c] = 0;
2419
+ if(p->fds[c].fd > 0) {
2420
+ file_descriptor_not_used(p->fds[c].fd);
2421
+ clear_pid_fd(&p->fds[c]);
2422
}
2423
2424
pid_t r = p->pid;
@@ -2572,9 +2673,10 @@ static inline void aggregate_pid_fds_on_targets(struct pid_stat *p) {
2673
reallocate_target_fds(u);
2674
reallocate_target_fds(g);
2675
2575
- int c, size = p->fds_size, *fds = p->fds;
2676
+ size_t c, size = p->fds_size;
2677
+ struct pid_fd *fds = p->fds;
2678
for(c = 0; c < size ;c++) {
2577
- int fd = fds[c];
2679
+ int fd = fds[c].fd;
2680
2681
if(likely(fd <= 0 || fd >= all_files_size))
2682
continue;
@@ -2753,6 +2855,9 @@ void send_resource_usage_to_netdata(usec_t dt) {
2855
"CHART netdata.apps_sizes '' 'Apps Plugin Files' 'files/s' apps.plugin netdata.apps_sizes line 140001 %1$d\n"
2856
"DIMENSION calls '' incremental 1 1\n"
2857
"DIMENSION files '' incremental 1 1\n"
2858
+ "DIMENSION filenames '' incremental 1 1\n"
2859
+ "DIMENSION inode_changes '' incremental 1 1\n"
2860
+ "DIMENSION link_changes '' incremental 1 1\n"
2861
"DIMENSION pids '' absolute 1 1\n"
2862
"DIMENSION fds '' absolute 1 1\n"
2863
"DIMENSION targets '' absolute 1 1\n"
@@ -2795,6 +2900,9 @@ void send_resource_usage_to_netdata(usec_t dt) {
2900
"BEGIN netdata.apps_sizes %llu\n"
2901
"SET calls = %zu\n"
2902
"SET files = %zu\n"
2903
+ "SET filenames = %zu\n"
2904
+ "SET inode_changes = %zu\n"
2905
+ "SET link_changes = %zu\n"
2906
"SET pids = %zu\n"
2907
"SET fds = %d\n"
2908
"SET targets = %zu\n"
@@ -2806,6 +2914,9 @@ void send_resource_usage_to_netdata(usec_t dt) {
2914
, dt
2915
, calls_counter
2916
, file_counter
2917
+ , filenames_allocated_counter
2918
+ , inodes_changed_counter
2919
+ , links_changed_counter
2920
, all_pids_count
2921
, all_files_len
2922
, apps_groups_targets_count
@@ -3348,6 +3459,19 @@ static void parse_args(int argc, char **argv)
3459
continue;
3460
}
3461
3462
+#ifndef __FreeBSD__
3463
+ if(strcmp("fds-cache-secs", argv[i]) == 0) {
3464
+ if(argc <= i + 1) {
3465
+ fprintf(stderr, "Parameter 'fds-cache-secs' requires a number as argument.\n");
3466
+ exit(1);
3467
+ }
3468
+ i++;
3469
+ max_fds_cache_seconds = str2i(argv[i]);
3470
+ if(max_fds_cache_seconds < 0) max_fds_cache_seconds = 0;
3471
+ continue;
3472
+ }
3473
+#endif
3474
+
3475
if(strcmp("no-childs", argv[i]) == 0 || strcmp("without-childs", argv[i]) == 0) {
3476
include_exited_childs = 0;
3477
continue;
@@ -3417,9 +3541,21 @@ static void parse_args(int argc, char **argv)
3541
" without-files enable / disable reporting files, sockets, pipes\n"
3542
" (default is enabled)\n"
3543
"\n"
3544
+#ifndef __FreeBSD__
3545
+ " fds-cache-secs N cache the files of processed for N seconds\n"
3546
+ " caching is adaptive per file (when a file\n"
3547
+ " is found, it starts at 0 and while the file\n"
3548
+ " remains open, it is incremented up to the\n"
3549
+ " max given)\n"
3550
+ " (default is %d seconds)\n"
3551
+ "\n"
3552
+#endif
3553
" version or -v or -V print program version and exit\n"
3554
"\n"
3555
, VERSION
3556
+#ifndef __FreeBSD__
3557
+ , max_fds_cache_seconds
3558
+#endif
3559
);
3560
exit(1);
3561
}