@cryptotaxi247 / netdata-1 / commits / 04ee49192

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 }