aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/JvmDumper.java
blob: 360a212646f021cb7d1861fc54321e5a9c10a834 (plain) (blame)
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
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.maintenance.servicedump;

import com.yahoo.vespa.hosted.node.admin.task.util.fs.ContainerPath;
import com.yahoo.yolean.concurrent.Sleeper;

import java.time.Duration;
import java.util.List;

import static com.yahoo.vespa.hosted.node.admin.maintenance.servicedump.Artifact.Classification.CONFIDENTIAL;
import static com.yahoo.vespa.hosted.node.admin.maintenance.servicedump.Artifact.Classification.INTERNAL;

/**
 * @author bjorncs
 */
class JvmDumper {
    private JvmDumper() {}

    static class HeapDump implements ArtifactProducer {
        @Override public String artifactName() { return "jvm-heap-dump"; }
        @Override public String description() { return "JVM heap dump"; }

        @Override
        public List<Artifact> produceArtifacts(Context ctx) {
            ContainerPath heapDumpFile = ctx.outputContainerPath().resolve("jvm-heap-dump.hprof");
            List<String> cmd = List.of(
                    "jmap", "-dump:live,format=b,file=" + heapDumpFile.pathInContainer(), Integer.toString(ctx.servicePid()));
            ctx.executeCommandInNode(cmd, true);
            return List.of(
                    Artifact.newBuilder().classification(CONFIDENTIAL).file(heapDumpFile).compressOnUpload().build());
        }
    }

    static class Jmap implements ArtifactProducer {
        @Override public String artifactName() { return "jvm-jmap"; }
        @Override public String description() { return "JVM jmap output"; }

        @Override
        public List<Artifact> produceArtifacts(Context ctx) {
            ContainerPath jmapReport = ctx.outputContainerPath().resolve("jvm-jmap.txt");
            List<String> cmd = List.of("bash", "-c", "jhsdb jmap --heap --pid " + ctx.servicePid() + " > " + jmapReport.pathInContainer());
            ctx.executeCommandInNode(cmd, true);
            return List.of(Artifact.newBuilder().classification(INTERNAL).file(jmapReport).build());
        }
    }

    static class Jstat implements ArtifactProducer {
        @Override public String artifactName() { return "jvm-jstat"; }
        @Override public String description() { return "JVM jstat output"; }

        @Override
        public List<Artifact> produceArtifacts(Context ctx) {
            ContainerPath jstatReport = ctx.outputContainerPath().resolve("jvm-jstat.txt");
            List<String> cmd = List.of("bash", "-c", "jstat -gcutil " + ctx.servicePid() + " > " + jstatReport.pathInContainer());
            ctx.executeCommandInNode(cmd, true);
            return List.of(Artifact.newBuilder().classification(INTERNAL).file(jstatReport).build());
        }
    }

    static class Jstack implements ArtifactProducer {
        @Override public String artifactName() { return "jvm-jstack"; }
        @Override public String description() { return "JVM jstack output"; }

        @Override
        public List<Artifact> produceArtifacts(Context ctx) {
            ContainerPath jstackReport = ctx.outputContainerPath().resolve("jvm-jstack.txt");
            ctx.executeCommandInNode(List.of("bash", "-c", "jstack " + ctx.servicePid() + " > " + jstackReport.pathInContainer()), true);
            return List.of(Artifact.newBuilder().classification(INTERNAL).file(jstackReport).build());
        }
    }

    static class JavaFlightRecorder implements ArtifactProducer {
        private final Sleeper sleeper;

        JavaFlightRecorder(Sleeper sleeper) { this.sleeper = sleeper; }

        @Override public String artifactName() { return "jvm-jfr"; }
        @Override public String description() { return "Java Flight Recorder recording"; }

        @Override
        public List<Artifact> produceArtifacts(ArtifactProducer.Context ctx) {
            int seconds = (int) (ctx.options().duration().orElse(30.0));
            ContainerPath outputFile = ctx.outputContainerPath().resolve("recording.jfr");
            List<String> startCommand = List.of("jcmd", Integer.toString(ctx.servicePid()), "JFR.start", "name=host-admin",
                    "path-to-gc-roots=true", "settings=profile", "filename=" + outputFile.pathInContainer(), "duration=" + seconds + "s");
            ctx.executeCommandInNode(startCommand, true);
            sleeper.sleep(Duration.ofSeconds(seconds).plusSeconds(1));
            int maxRetries = 10;
            List<String> checkCommand = List.of("jcmd", Integer.toString(ctx.servicePid()), "JFR.check", "name=host-admin");
            for (int i = 0; i < maxRetries; i++) {
                boolean stillRunning = ctx.executeCommandInNode(checkCommand, true).getOutputLines().stream()
                        .anyMatch(l -> l.contains("name=host-admin") && l.contains("running"));
                if (!stillRunning) {
                    Artifact a = Artifact.newBuilder()
                            .classification(CONFIDENTIAL).file(outputFile).compressOnUpload().build();
                    return List.of(a);
                }
                sleeper.sleep(Duration.ofSeconds(1));
            }
            throw new RuntimeException("Failed to wait for JFR dump to complete after " + maxRetries + " retries");
        }
    }
}