com.cloudbees.jenkins.support.impl.SlaveLogs.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudbees.jenkins.support.impl.SlaveLogs.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2015, CloudBees, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.cloudbees.jenkins.support.impl;

import com.cloudbees.jenkins.support.SupportPlugin;
import com.cloudbees.jenkins.support.api.Component;
import com.cloudbees.jenkins.support.api.Container;
import com.cloudbees.jenkins.support.api.FileContent;
import com.cloudbees.jenkins.support.timer.FileListCapComponent;
import com.google.common.collect.Lists;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.security.Permission;
import hudson.slaves.SlaveComputer;
import hudson.util.DaemonThreadFactory;
import hudson.util.ExceptionCatchingThreadFactory;
import jenkins.model.Jenkins;

import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import static com.cloudbees.jenkins.support.SupportPlugin.SUPPORT_DIRECTORY_NAME;

/**
 * Adds the agent logs from all of the machines
 */
@Extension(ordinal = 100.0) // put this first as largest content and can let the slower ones complete.
public class SlaveLogs extends Component {
    private static final Logger LOGGER = Logger.getLogger(SlaveLogs.class.getCanonicalName());

    @NonNull
    @Override
    public Set<Permission> getRequiredPermissions() {
        return Collections.singleton(Jenkins.ADMINISTER);
    }

    @NonNull
    @Override
    public String getDisplayName() {
        return "Agent Log Recorders";
    }

    @Override
    public boolean isSelectedByDefault() {
        return false;
    }

    @Override
    public void addContents(@NonNull Container container) {
        // expensive remote computation are pooled together and executed later concurrently across all the agents
        List<java.util.concurrent.Callable<List<FileContent>>> tasks = Lists.newArrayList();
        SmartLogFetcher logFetcher = new SmartLogFetcher("cache", new LogFilenameFilter()); // id is awkward because of backward compatibility
        SmartLogFetcher winswLogFetcher = new SmartLogFetcher("winsw", new WinswLogfileFilter());

        for (final Node node : Jenkins.get().getNodes()) {
            if (node.toComputer() instanceof SlaveComputer) {
                container.add(new LogRecordContent("nodes/slave/" + node.getNodeName() + "/jenkins.log") {
                    @Override
                    public Iterable<LogRecord> getLogRecords() throws IOException {
                        Computer computer = node.toComputer();
                        if (computer == null) {
                            return Collections.emptyList();
                        } else {
                            try {
                                return Lists.reverse(new ArrayList<>(computer.getLogRecords()));
                            } catch (InterruptedException e) {
                                throw new IOException(e);
                            }
                        }
                    }
                });
            }

            addSlaveJulLogRecords(container, tasks, node, logFetcher);
            addWinsStdoutStderrLog(tasks, node, winswLogFetcher);
        }

        // execute all the expensive computations in parallel to speed up the time
        if (!tasks.isEmpty()) {
            ExecutorService service = Executors.newFixedThreadPool(
                    Math.max(1, Math.min(Runtime.getRuntime().availableProcessors() * 2, tasks.size())),
                    new ExceptionCatchingThreadFactory(new DaemonThreadFactory()));
            try {
                long expiresNanoTime = System.nanoTime()
                        + TimeUnit.SECONDS.toNanos(SupportPlugin.REMOTE_OPERATION_CACHE_TIMEOUT_SEC);
                for (java.util.concurrent.Future<List<FileContent>> r : service.invokeAll(tasks,
                        SupportPlugin.REMOTE_OPERATION_CACHE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
                    try {
                        for (FileContent c : r.get(Math.max(1, expiresNanoTime - System.nanoTime()),
                                TimeUnit.NANOSECONDS)) {
                            container.add(c);
                        }
                    } catch (ExecutionException e) {
                        LOGGER.log(Level.WARNING, "Could not retrieve some of the remote node extra logs", e);
                    } catch (TimeoutException e) {
                        LOGGER.log(Level.WARNING, "Could not retrieve some of the remote node extra logs", e);
                        r.cancel(false);
                    }
                }
            } catch (InterruptedException e) {
                LOGGER.log(Level.WARNING, "Could not retrieve some of the remote node extra logs", e);
            } finally {
                service.shutdown();
            }
        }

    }

    /**
     * Captures a "recent" (but still fairly large number of) j.u.l entries written on this agent.
     *
     * @see JenkinsLogs#addMasterJulLogRecords(Container)
     */
    private void addSlaveJulLogRecords(Container result,
            List<java.util.concurrent.Callable<List<FileContent>>> tasks, final Node node,
            final SmartLogFetcher logFetcher) {
        final FilePath rootPath = node.getRootPath();
        if (rootPath != null) {
            // rotated log files stored on the disk
            tasks.add(new java.util.concurrent.Callable<List<FileContent>>() {
                public List<FileContent> call() throws Exception {
                    List<FileContent> result = new ArrayList<FileContent>();
                    FilePath supportPath = rootPath.child(SUPPORT_DIRECTORY_NAME);
                    if (supportPath.isDirectory()) {
                        final Map<String, File> logFiles = logFetcher.forNode(node).getLogFiles(supportPath);
                        for (Map.Entry<String, File> entry : logFiles.entrySet()) {
                            result.add(
                                    new FileContent("nodes/slave/" + node.getNodeName() + "/logs/" + entry.getKey(),
                                            entry.getValue()));
                        }
                    }
                    return result;
                }
            });
        }

        // this file captures the most recent of those that are still kept around in memory.
        // this overlaps with Jenkins.logRecords, and also overlaps with what's written in files,
        // but added nonetheless just in case.
        //
        // should be ignorable.
        result.add(new LogRecordContent("nodes/slave/" + node.getNodeName() + "/logs/all_memory_buffer.log") {
            @Override
            public Iterable<LogRecord> getLogRecords() throws IOException {
                try {
                    return SupportPlugin.getInstance().getAllLogRecords(node);
                } catch (InterruptedException e) {
                    throw (IOException) new InterruptedIOException().initCause(e);
                }
            }
        });
    }

    /**
     * Captures stdout/stderr log files produced by winsw.
     */
    private void addWinsStdoutStderrLog(List<java.util.concurrent.Callable<List<FileContent>>> tasks,
            final Node node, final SmartLogFetcher logFetcher) {
        final FilePath rootPath = node.getRootPath();
        if (rootPath != null) {
            // rotated log files stored on the disk
            tasks.add(new java.util.concurrent.Callable<List<FileContent>>() {
                public List<FileContent> call() throws Exception {
                    List<FileContent> result = new ArrayList<FileContent>();
                    final Map<String, File> logFiles = logFetcher.forNode(node).getLogFiles(rootPath);
                    for (Map.Entry<String, File> entry : logFiles.entrySet()) {
                        result.add(new FileContent(
                                "nodes/slave/" + node.getNodeName() + "/logs/winsw/" + entry.getKey(),
                                entry.getValue(), FileListCapComponent.MAX_FILE_SIZE));
                    }
                    return result;
                }
            });
        }
    }

}